React Hooks - 更新父组件而不触发无限循环

Sve*_*ies 4 javascript reactjs react-hooks use-effect

假设我有一个父组件,它呈现一个子组件,而子组件所做的就是保持某种状态,并在其自身状态发生变化时触发 onChange 处理程序。请参阅下面的代码或 CodeSandbox。

代码沙盒示例

这段代码进入无限循环,我想摆脱这种行为。

可能的解决方案,但我不喜欢的解决方案:

1:我可以将所有状态放在父组件中,并用父组件控制子组件,但这并不是我真正想要的。在现实生活中,我的子组件不仅仅负责一个简单的计数器,而且我想从父组件中轻松使用它。子组件内部有复杂的行为,我想向父组件传达一些简单的更改。或者这在 React 中是绝对不能做的吗?(保持子级状态并触发父级状态更新的更改)我想说这不一定吗?

2:我还可以从handleInternalChange 处理程序中触发onChange 处理程序。而且,也不是我想要的。在现实生活中,我的子组件将从组件本身的几个不同位置进行更新,因此状态更改是观察和触发 onChange 父组件的最优雅的方式。

3:我可以省略 useEffect 依赖项数组中的 onChange 依赖项。不推荐这样做,React社区参考了这个解释。我理解,只是我觉得这种情况是例外?** 另外,我使用 CRA,它带有开箱即用的很棒的 linter 等,如果我从依赖项中删除 onChange 处理程序,linter 会抱怨它,而且我不喜欢开始制定自己的班轮规则。对于像我这样的简单用例,社区设置的 linter 应该可以正常工作。

我认为会发生什么认为会发生的是,父级被更新,然后重新渲染整个事物,并且不知何故, onChange 处理程序也被“更改”。据我所说,该函数实际上并没有改变,但 React 认为(或知道)它改变了,因此它在子组件中再次触发 useEffect 调用,然后无限循环诞生。

但是,就我而言, onChange 函数没有改变。那么为什么会触发 useEffect 调用呢?我怎样才能防止这种情况发生?

import React, { useState, useEffect } from "react";

const Comp = ({ onChange }) => {
  const [internalState, setInternalState] = useState(0);
  const handleChange = () => {
    setInternalState(internalState + 1);
  };

  useEffect(() => {
    const result = internalState.toString();
    onChange(result);
  }, [internalState, onChange]);

  return (
    <div onClick={handleChange}>
      CLICK ME
      <div>{`INTERNAL NUM: ${internalState}`}</div>
    </div>
  );
};

export default function App() {
  const [state, setState] = useState("");
  const handleChange = () => setState(state + 1);
  return (
    <div className="App">
      <h3>{`STATE: ${state}`}</h3>
      <Comp onChange={handleChange} />
    </div>
  );
}
Run Code Online (Sandbox Code Playgroud)

** 经过一段时间后,在其他情况下,onChange 属性当然可以更改,只需为该属性分配不同的函数即可所以这个规则对我来说是非常清楚的。只是(如上一段所述)为什么在这种情况下它的行为会发生变化?因为我的功能根本没有改变。

Viv*_*vek 6

您应该使用 useCallback 挂钩包装 handleChange 方法,以便它被创建一次。

const handleChange = useCallback(() => setState(state + 1),[]);
Run Code Online (Sandbox Code Playgroud)

发生无限循环是因为您onChange在组件中添加了方法作为 useEffect 的依赖项<Comp />

useEffect 接受一系列依赖项,并在依赖项之一发生更改时运行效果。

由于您已添加 onchange 处理程序作为依赖项,因此每次父组件重新渲染时,handleChange都会创建一个新的方法实例,该实例不等于以前的 handlechange 方法。

组件渲染流程将是这样的:

  1. <App />组件创建handleChange方法并将其传递给<Comp />
  2. 中的 Useffect<Comp />将在初始渲染后被调用,并从那里<App />调用组件的 handleChange 方法。
  3. 组件中的状态发生变化<App />并重新渲染它。重新渲染 handleChange 的新实例时,会创建并将 onChange 属性传递给<Comp />组件。
  4. 由于先前和新的 onChange prop 的值不同,因此将触发 useEffect,这将再次更新父组件的状态,并且此循环将继续。

为了防止这种情况,handleChange方法应该用useCallback包装。传递给 useCallback 钩子的回调函数将被记忆,因此当子组件比较新旧 prop 时,它们保持相等。

{} === {}  // false
[] === []  // false
(() => {}) == (() => {})  //false
Run Code Online (Sandbox Code Playgroud)