为什么 React 需要另一个渲染来避免状态更新?

Dup*_*cas 13 reactjs

考虑以下 Component

const Component = () =>{
    const [state, setState] = useState(null)

    const onClick = () => setState('foo')        

    console.log(state)

    return <button onClick={onClick}> Change </button>   
}
Run Code Online (Sandbox Code Playgroud)

编辑 sad-thompson-s3l3i

  • 在按下按钮之前 console只打印null
  • 第一次按下按钮console打印foo
  • 第二次按下按钮并console打印foo
  • 第三次和向前console不打印任何东西

我知道这console不会打印任何内容,因为我正在调用setState传递与当前状态相同的值,而 React 正在救助状态更新。这部分对我来说很清楚。我的问题是关于以下断言

请注意,在退出之前,React 可能仍需要再次渲染该特定组件。这不应该是一个问题,因为 React 不会不必要地“深入”到树中。如果您在渲染时进行昂贵的计算,您可以使用 useMemo 优化它们。

为什么需要这个额外的渲染?我的意思是,不是第二次点击后就Object.is返回false了吗?

osd*_*amv 5

useState 内部是一个useReducer,带有一个basicReducer,钩子使用更改队列来更新状态

AFAIK 在查看代码后,这是 memoizedState 在队列中未完全处理的情况,因为没有使用钩子 useMemo 的精细控制

  • src 代码的注释“TODO:不确定这是否是所需的语义,但这就是我们为 gDSFP 所做的。我不记得为什么。” 罗弗尔 (2认同)

Dup*_*cas 5

我从同一问题中删除的答案中得到了满意的答案。dc指出Dan Abrahmov在这个老的github问题中给出了答案。引用丹:

我认为当时我们决定,在我们再次尝试渲染之前,我们实际上并不知道在所有情况下是否安全。这里的“救助”意味着它不会继续渲染孩子。但是重新运行相同的函数可能是必要的(例如,如果一个减速器是内联的,并且我们不知道是否要退出,直到我们在下一次渲染时重新运行减速器)。因此,为了保持一致性,我们总是在渲染阶段更新时重新运行它。