我是否偶然创建了一个全球状态?

时间:2019-07-29 14:33:17

标签: javascript reactjs state

我正在构建一个React Web应用程序,该应用程序分为可通过react-tabs访问的多个组件:

import React from 'react';
import ReactDOM from 'react-dom';
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs';
import a from './components/a';
import b from './components/b';

const TabNavigator = () => (
    <Tabs>
        <TabList>
            <Tab> A </Tab>
            <Tab> B </Tab>
        </TabList>

        <TabPanel>
            <a />
        </TabPanel>
        <TabPanel>
            <b />
        </TabPanel>
    </Tabs>
);

ReactDOM.render(<TabNavigator />, document.getElementById('root'));

每个选项卡都是他自己的组件/子系统,当访问该选项卡时将重新呈现该组件/子系统。在每个标签中,我都使用一个JSON文件的数据。像这样将这些数据加载到每个组件的状态:

constructor(props) {
    super(props);

    this.state = {
        data: json
    };
}

我现在正在更改组件之一的状态,以使用新数据触发重新渲染:

this.setState({
    data: editedJson
});

到目前为止还不错,但是当我现在切换到另一个选项卡/组件this.state.data时,那里也发生了变化-为什么会发生这种情况?组件之间是否共享状态?

编辑:这是一个MVCE,您可以在其中更改B中的状态,它也将在A中更改:

Edit tabs-parent-pass-state

3 个答案:

答案 0 :(得分:1)

似乎当您导入该JSON文件时,您正在为其创建一个内存中版本,然后两个组件都将其引用。如果按照@Christiaan的建议在构造函数中复制值(散布值),则可以避免对原始值进行变异,这是一种不好的做法。

Edit distracted-poitras-jg1eb

更好的建议可能是在公共祖先中将JSON导入一次,并将引用或复制的实例传递给每个选项卡……这实际上取决于每个选项卡真正需要对该数据执行的操作。

您应该注意,每当您切换选项卡时,每个选项卡都会被挂载/取消安装,因此构造函数逻辑每次都会运行。如果您希望状态更改继续存在,则必须将其放到父组件(即index.js中的Tabs组件)中,并将所需的数据传递给每个子选项卡。

Edit tabs-parent-pass-state

实际上有几个因素对您不利。首先是导入被缓存(explanation here)的事实,因此,每次安装选项卡时,json导入都会返回缓存的引用,该引用存储在data中,并且在构造函数中,您将该引用值保存到state.data。第二个问题是您的setState函数不是一个纯粹的函数。它只是再次复制引用,并在真正要创建新数组的时候更改它指向的数组,然后在添加新元素之前浅拷贝所有元素。下面是另一个沙箱,应该可以帮助说明这一点。

Edit mutate-imported-json

答案 1 :(得分:1)

我能够像这样复制您的问题:

import React from "react";
import ReactDOM from "react-dom";
import { Tab, Tabs, TabList, TabPanel } from "react-tabs";

import "./styles.css";

let json = { title: "I am a title" };

function Content({tab}) {
  const [state, setState] = React.useState(json);

  return (
    <div>
      <h1>{tab}</h1>
      <h2>{state.title}</h2>
      <label>New Title</label>
      <input type="text" value={state.title} onChange={handleChange} />
    </div>
  );

  function handleChange(e) {
    json = { title: e.target.value };
    setState(json);
  }
}

function TabNavigator() {
  return (
    <Tabs>
      <TabList>
        <Tab>A</Tab>
        <Tab>B</Tab>
      </TabList>
      <TabPanel><Content name="A" /></TabPanel>
      <TabPanel><Content name="B"/></TabPanel>
    </Tabs>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<TabNavigator />, rootElement);

当您更新“全局”对象json,然后使用它来更新component状态时,就会出现问题。参见handleChange函数;我先更新json对象,然后使用它设置新组件state

当您转移到另一个tab时,将创建一个新组件,该组件的状态将从“全局” json对象实例化,因此,其内容与其他tab。当您在它们之间切换时,将重复此过程。

如果您删除此分配以更新state,那么问题就解决了(只需setState({title: e.target.value})。但是你不能坚持改变。为了解决这个问题,我建议使用React Context。 Here is a link to a CodeSandbox where you can see it in action

希望对您有帮助。

答案 2 :(得分:0)

您所有组件中的json具有相同的指针,因此所有组件都可以访问同一对象并进行更改。

要解决此问题,必须为每个组件创建一个全新的对象,如下所示:

this.state = {
    data: { ...json }
};