渲染期间直接在组件主体中将状态更新为相同值会导致无限循环

Teh*_*ila 13 javascript infinite-loop reactjs react-hooks

假设我有这个简单的虚拟组件:

const Component = () => {

  const [state, setState] = useState(1);

  setState(1);

  return <div>Component</div>
}
Run Code Online (Sandbox Code Playgroud)

在此代码中,我直接在组件主体中将状态更新为与之前相同的值。但是,即使值保持不变,这也会导致过多的重新渲染。

据我所知,在 中React.useState,如果状态值更新为与之前相同的值 - React 不会重新渲染组件。那么为什么会发生在这里呢?

但是useEffect,如果我尝试在组件主体中而不是直接在组件主体中执行类似的操作:

const Component = () => {

  const [state, setState] = useState(1);

  useEffect(() => {
    setState(1);
  }, [state])

  return <div>Component</div>
}
Run Code Online (Sandbox Code Playgroud)

不会导致任何无限循环,并且完全符合如果状态保持不变,React 不会重新渲染组件的规则。

所以我的问题是:为什么当我直接在组件主体中执行此操作时会导致无限循环,而在组件主体中却useEffect不会?

有人对此有一些“幕后”解释吗?

Dre*_*ese 10

长话短说

第一个示例是无意的副作用,将无条件触发重新渲染,而第二个示例是有意的副作用,并允许 React 组件生命周期按预期运行。

回答

我认为当 React 调用组件的渲染方法来计算下一个渲染周期的差异时,您正在将组件生命周期的“渲染阶段”与我们在React时的“提交阶段”期间通常所说的“渲染周期”混为一谈。已更新 DOM。

参见组件生命周期图:

在此输入图像描述

请注意,在 React 函数组件中,整个函数体都是“render”方法,函数的返回值是我们想要刷新或提交到 DOM 的值。现在我们都应该知道,React 组件的“render”方法被认为是一个没有副作用的纯函数。换句话说,渲染的结果是状态和道具的纯函数。

在第一个示例中,排队状态更新是正常组件生命周期(即 mount、update、unmount )之外调用的无意副作用

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

  setState(1); // <-- unintentional side-effect

  return <div>Component</div>;
};
Run Code Online (Sandbox Code Playgroud)

它在“渲染阶段”触发重新渲染。React 组件永远没有机会完成渲染周期,因此没有什么可以“比较”或摆脱的,因此渲染循环发生。

另一个例子是排队状态更新是故意的副作用。该钩子在下一个 UI 更改刷新或提交到 DOM 后useEffect的渲染周期结束时运行。

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

  useEffect(() => {
    setState(1); // <-- intentional side-effect
  }, [state]);

  return <div>Component</div>;
}
Run Code Online (Sandbox Code Playgroud)

钩子大致useEffect是相当于类组件的、和生命周期方法的函数组件。无论依赖关系如何,在组件安装时都保证至少运行一次。该效果将运行一次并排队状态更新。React 将“看到”排队的值与当前状态值相同,并且不会触发重新渲染。componentDidMountcomponentDidUpdatecomponentWillUnmount

这里的要点是不要React 组件中编写无意和意外的副作用,因为这会导致和/或导致错误代码。

  • 这就是对此的完美解释。太感谢了! (2认同)