【问题标题】:Why is react setState method immutable?为什么反应 setState 方法是不可变的?
【发布时间】:2017-09-15 06:15:05
【问题描述】:

以下来自 React 教程:

const squares = this.state.squares.slice();
squares[i] = 'X';
this.setState({squares: squares});

此代码更改复制 state.squares 并将其分配给原始 state.squares。最后这改变了原来的state.squares,所以我认为这与下面的可变代码没有什么不同:

this.state.squares[i] = 'X';

有什么不同吗?

【问题讨论】:

    标签: javascript reactjs immutability difference setstate


    【解决方案1】:

    我也想知道这个问题。但我发现答案并不令人满意。所以这是我的看法

    答案实际上写在文档本身 http://reactjs.org/docs/state-and-lifecycle.html#do-not-modify-state-directly

    所以第一个原因,setState() 触发 render()

    第二个原因,状态改变在 React 中是异步的,所以当你仍然引用旧的未更改状态时,可能有一些其他组件在后台改变状态,这会导致错误的状态值

    【讨论】:

      【解决方案2】:

      这段代码是不可变的,因为使用了slice() 方法。如果你尝试:

      someState = {squares: [1,2,3,4,5]}
      squares = someState.squares.slice()
      

      您将获得由slice() 方法创建的新数组。

      你可以这样测试它:

      squares = someState.squares.slice()
      squares2 = someState.squares
      squares[0] = 9    // doesn't change someState
      squares2[1] = 9   // changes someState
      someState.squares // [1,9,3,4,5] - as I said
      

      如果您对this.setState({squares: squares}); 有疑问 - 是的,当然在运行后您有新状态,但实际上这个状态不是修改过的旧状态对象,而是从旧部分创建的新对象。所以如果你尝试:

      oldState = this.state
      this.setState({squares: squares})
      

      您会看到新状态与保存的旧状态不同:

      this.state == oldState //false
      

      如果this.state.squares[i] = 'X'; oldState 也会被修改,这正是我们所说的可变性。旧状态的所有复制部分都会随之改变,这会导致很多问题。

      【讨论】:

        【解决方案3】:

        你可以这样做,但你不应该这样做,背后的原因是,如果你使用

        this.state.squares[i] = 'X';
        

        它将被下一个覆盖

        this.setState({squares: squares});
        

        因此,您的应用将没有准确的数据。

        来自文档:

        永远不要直接改变 this.state,因为之后调用 setState() 可能 替换您所做的突变。将 this.state 视为 不可变。

        https://facebook.github.io/react/docs/react-component.html#state查看更多信息

        【讨论】:

          【解决方案4】:

          不要直接改变状态,这是文档所说的。

          我正在编写一个待办事项列表,并犯了直接改变状态的同样错误,但在我的情况下,我使用了钩子。我不明白为什么运行 setState 时屏幕没有重新渲染。状态确实发生了变异(由console.log 确认),甚至useEffect 运行,因为它检测到更新的依赖项。

          扩展 Purecomponent 的 React 类也具有相同的行为。有趣的是,如果我要使用扩展 React.Component 的类并使用 this.setState 函数,应用程序会重新渲染屏幕。

          在我询问和学习之后,事实证明我需要将状态视为不可变的。

          这段代码: var newwrongArr = this.state.arr; 不复制任何东西,它只引用值,证明如果你改变newwrongArr,状态也会改变。

          如果我们要复制,代码应该是:

          var newArr = [...this.state.arr]; //the three dots are spread operator
          

          或其他一些功能,如Object.assign().

          我的结论是,如果我们改变 newwrongArrsetStatearr(newwrongArr),我认为 React 钩子会决定重新渲染是不必要的,因为它认为 newwrongArr 与 state 的值相同(尽管 state 发生了变化,但不会发生重新渲染)。但是如果我们要使用扩展运算符复制值,那么 setState 会认为需要重新渲染,这是预期的结果。

          抱歉,回答太长了。

          【讨论】:

            【解决方案5】:

            同意@Karol Selak,我想在这里举个例子让事情更清楚:

            const squares = this.state.squares.slice();
            squares[i] = 'X';
            this.setState({squares: squares});
            

            此代码将执行以下步骤:

            • 创建一个新的 squares 变量,放置在内存中,例如 12345
            • 将索引 i 处的项目设置为“X”(此时,旧方块和新方块也发生了变化,因为数组中的对象是引用)

            我们使用的时候,比如useEffect hook如下:

            useEffect(() => {
               // do something
            }[state.squares])
            

            React 会比较旧方块和新方块,在这种情况下,2 这些是不同的,因为内存中的地址不同。然后 useEffect 将运行。

            第二种情况:

            this.state.squares[i] = 'X';
            

            useEffect 不起作用

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2018-08-17
              • 2020-09-24
              • 2015-08-27
              相关资源
              最近更新 更多