首页 > 解决方案 > 反应重新渲染循环

问题描述

我目前正在尝试了解 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])} />;
}

该示例包含一个具有状态的 Parent ( 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. onChangeChild 中效果的依赖项中删除。这“解决”了重新渲染,对于我的用例来说绝对可以接受。但是,据我所知,这是不好的做法,因为onChange在效果内使用。此外,ESLint 将此指示为警告。
  2. 在状态中使用“原始”数字,而不是数组。这也将摆脱重新渲染。然而,这仅在这个最小示例中是可接受的,因为只使用了一个数字。对于数字的动态计数,此解决方法不可行。

useCallback也无济于事,只是“冒泡”了功能的重新创建onChange

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

标签: arraysreactjsdependenciesstaterender

解决方案


为什么要更改 onChange?

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

由于React 在渲染之前与之前的 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]), []);

请注意,我们记住该函数只是因为它的用例不平凡(提防过早优化)。


推荐阅读