反应重新渲染循环

时间:2021-03-04 16:05:47

标签: arrays reactjs dependencies state render

我目前正在尝试在重新渲染组件或特别是重新创建(回调)函数时了解 React 的内部工作原理。

在这样做的过程中,我遇到了一个我无法理解的现象。它(仅)在具有包含数组的状态时发生。这是显示“问题”的最小代码:

import { useEffect, useState } from "react";

export function Child({ value, onChange }) {
  const [internalValue, setInternalValue] = useState(value);

  // ... stuff interacting with internalValue

  useEffect(() => {
    onChange(internalValue);
  }, [onChange, internalValue]);

  return <div>{value}</div>;
}

export default function App() {
  const [state, setState] = useState([9.0]);
  return <Child value={state[0]} onChange={(v) => setState([v])} />;
}

该示例包含一个带有状态的父 (App) 组件,它是一个由单个数字组成的数组,该数组被赋予 Child 组件。 Child 可能会做一些内部工作并使用 setInternalValue 设置内部状态,这反过来会触发效果。此效果将引发 onChange 函数,更新父状态数组的值。 (请注意,此示例已最小化以显示效果。数组将具有多个值,其中显示了每个子组件) 然而,此示例导致使用以下控制台警告在整个过程中出现:

Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.

调试显示,由于 onChange 被更改而发生重新渲染。但是,我不明白这一点。为什么要更改 onChange? internalState 和 state 都不会在任何地方更改。

我发现了两种解决方法:

  1. 从 Child 中效果的依赖项中删除 onChange。这“解决”了重新渲染,对于我的用例来说是绝对可以接受的。但是,据我所知,这是不好的做法,因为 onChange 在效果中使用。此外,ESLint 将此表示为警告。
  2. 在状态中使用“原始”数字,而不是数组。这也将摆脱重新渲染。然而,这仅在这个最小示例中是可以接受的,因为只使用了一个数字。对于动态计数,此解决方法不可行。

useCallback 也没有帮助,只是“冒泡”重新创建 onChange 函数。

所以我的问题是:React 状态(包含数组)更新的处理方式是否不同,并且忽略此处的依赖项是否有效?执行此操作的正确方法是什么?

1 个答案:

答案 0 :(得分:1)

<块引用>

为什么要更改 onChange?

在每次渲染时,您创建一个新的匿名函数 (v) => setState([v])

由于 React makes 在渲染之前与之前的 props 进行了浅比较,所以它总是导致渲染,因为在 Javascript 中:

const y = () => {}
const x = () => {}

x !== y // always true

// In your case
const onChangeFromPreviousRender = (v) => setState([v])
const onChangeInCurrentRender = (v) => setState([v])

onChangeFromPreviousRender !== onChangeInCurrentRender
<块引用>

这样做的正确方法是什么?

有两种方法可以纠正它,因为 setState 保证是稳定的,您可以只传递 setter 并在组件本身中使用您的逻辑:

// state[0] is primitive
// setState stable
<Child value={state[0]} onChange={setState} />

  useEffect(() => {
    // set as array
    onChange([internalValue]);
  }, [onChange, internalValue]);

或者,记忆函数将保证相同的身份。

const onChange = useCallback(v => setState([v]), []);

请注意,我们记住该函数只是因为其非平凡用例(注意过早优化)。