【问题标题】:What exactly is prevState in a functional component's setState?功能组件的 setState 中的 prevState 到底是什么?
【发布时间】:2020-05-23 05:45:45
【问题描述】:

返回从useState 钩子获得的setState 中的(更改的)先前状态似乎不会改变状态。直接运行以下 sn -p

function App(){
  const [state, setState] = React.useState([{x: 0}])
  function changeCount(){
    setState(prevState => {
      console.log('before', prevState[0])
      const newState = [...prevState]
      newState[0].x += 1 //the shallow copy newState could potentially change state
      console.log('after', prevState[0])//here x gets bigger as expected
      return prevState //instead of newState we return the changed prevState
    })
  }
  //checking state shows that x remained 0
  return <div className='square' onClick={changeCount}>{state[0].x}</div>
}
ReactDOM.render(<App/>, document.getElementById('root'))
.square{
  width: 100px;
  height: 100px;
  background: orange;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
<div id='root'></div>
通过单击方块我们触发setState。在setState 中,我们制作了先前状态的浅拷贝newState。通过更改副本,我们在控制台确认时更改了prevState(可能是无意的)。返回已更改的先前状态表单setState 但不会更改状态,因为计数仍为 0。如果我们要返回 newState,则行为将符合预期。

重复这个,表明prevState 变大了,它似乎不再代表之前的状态。

为什么会这样?我在codepen...上做了这个最小的例子...

【问题讨论】:

  • 这是一个很好的问题,试图找出发生了什么。
  • @gaditzkhori 这是一个浅拷贝,这就是为什么 prevState 和 newState 都在修改相同的 x 值,并且在每次渲染时 prevState 实际上都获得了更新的先前值,那么为什么我们不能将它设置为状态以及为什么不显示在 div 中?
  • setState 将先前的状态对象与您返回的对象进行比较。如果这两个都引用同一个对象,则不会进行渲染。尽管 prevState 已经更新,但它仍然是同一个对象。
  • newState 是一个浅拷贝,但你更改了x,这是相同的参考。React 不知道它被深度更改,因此不会重新渲染。所以基本上它是纯 js 而不是一些 React魔术。这就是浅层渲染的全部想法。
  • 你是对的! prevState 会按应有的方式更新,但由于对象的深度变化,不会发生重新渲染。通过更改一些额外的虚拟状态变量来强制重新渲染表明计数正确上升

标签: javascript reactjs state react-hooks react-state-management


【解决方案1】:

考虑到对象分配只是一个引用分配,而不是副本

obj1 = {x:42, y:99};
obj2 = obj1;   // obj1 and obj2 both reference the same object
obj1.x += 1;   
obj2.y += 1;
console.log(obj1.x, obj1.y);
console.log(obj2.x, obj2.y);  // prints the same thing since obj1 and obj2 are the same object

在上面的示例中,obj1 被初始化为指向具有属性 x 和 y 的新对象。当obj2=obj1 生成时,这不是将obj1 复制到obj2 中,而是obj1 和obj2 现在引用同一个对象。

因此,当 console.log 语句打印时,它们会打印相同的内容,因为它们都在打印来自同一对象的属性值。

同样,当从 prevState 到 newState 的浅拷贝发生时,也会对原始对象进行额外引用。

obj = {x:42, y:99};
prevState[0] = obj;     // prevState[0] is a reference to obj.  prevState[0] and obj point to the same exact thing

newState = [...prevState];  // shallow copy, but newState[0] is an object reference.  newState[0] and prevState[0] both point to "obj"

newState[0].x += 1;         // is actually updating the original object assigned to prevState[0]

【讨论】:

  • 感谢您的精彩解释!我对这样一个事实感到困惑,尽管 prevState 像预期的那样发生了变化,但人们并没有观察到新的状态。原因是,没有重新渲染,因为状态的变化是一个深刻的变化,而对象本身仍然是同一个对象。没有改变状态对象,没有重新渲染......
  • @Michael 即使返回的是同一个对象(prevState),因为它的值发生了变化(x 的值),为什么 React 不考虑重新渲染?
  • @Tick20 因为状态比较只是浅比较(即相同的对象引用不会导致重新渲染)。同样相关的是功能组件中的setState 仅进行浅层合并。
猜你喜欢
  • 2011-08-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-10-16
  • 1970-01-01
  • 2019-12-28
  • 2019-07-15
  • 2020-05-17
相关资源
最近更新 更多