为什么每次渲染都会调用来自 `useEffect` 的清理函数?

IT技术 javascript reactjs react-hooks use-effect
2021-04-12 03:30:51

我一直在学习 React,我读到从返回的函数useEffect是为了进行清理,而 React 在组件卸载时执行清理。

因此,我对其进行了一些试验,但在以下示例中发现,每次组件重新渲染时都会调用该函数,而不是仅在从 DOM 卸载时调用该函数,即每次组件重新渲染时都会调用该函数console.log("unmount");

这是为什么?

function Something({ setShow }) {
  const [array, setArray] = useState([]);
  const myRef = useRef(null);

  useEffect(() => {
    const id = setInterval(() => {
      setArray(array.concat("hello"));
    }, 3000);
    myRef.current = id;
    return () => {
      console.log("unmount");
      clearInterval(myRef.current);
    };
  }, [array]);

  const unmount = () => {
    setShow(false);
  };

  return (
    <div>
      {array.map((item, index) => {
        return (
          <p key={index}>
            {Array(index + 1)
              .fill(item)
              .join("")}
          </p>
        );
      })}
      <button onClick={() => unmount()}>close</button>
    </div>
  );
}

function App() {
  const [show, setShow] = useState(true);

  return show ? <Something setShow={setShow} /> : null;
}

实例:https : //codesandbox.io/s/vigilant-leavitt-z1jd2

5个回答

当组件卸载时,React 执行清理。

我不确定你在哪里读到的,但这个说法是不正确的。当对该钩子的依赖关系发生变化并且效果钩子需要使用新值再次运行时,React 会执行清理工作此行为是有意保持视图对更改数据的react性。离开官方示例,假设一个应用程序从朋友的个人资料中订阅状态更新。作为您的好朋友,您决定与他们成为朋友并与其他人成为朋友。现在该应用程序需要取消订阅前一个朋友的状态更新并听取新朋友的更新。这很自然,也很容易通过useEffect工作方式实现

 useEffect(() => { 
    chatAPI.subscribe(props.friend.id);

    return () => chatAPI.unsubscribe(props.friend.id);
  }, [ props.friend.id ])

通过在依赖列表中包含好友 id,我们可以指示只有当好友 id 更改时才需要运行钩子。

在您的示例中,您已array在依赖项列表中指定了,并且您正在以设定的时间间隔更改数组。每次更改数组时,钩子都会重新运行。

您可以简单地通过从依赖项列表中删除数组并使用setState钩子的回调版本来实现正确的功能回调版本总是对上一版本的状态进行操作,因此不需要每次数组变化时都刷新钩子。

  useEffect(() => {
    const id = setInterval(() => setArray(array => [ ...array, "hello" ]), 3000);

    return () => {
      console.log("unmount");
      clearInterval(id);
    };
  }, []);

一些额外的反馈是直接使用 id ,clearInterval因为当您创建清理函数时关闭(捕获)该值。无需将其保存到 ref。

继续阅读,您会发现这不仅仅是在组件卸载时。这就是我要说的重点。
2021-05-25 03:30:51
来自 React:“当组件卸载时,React 会执行清理工作。但是,正如我们之前所了解的,效果会在每次渲染时运行,而不仅仅是一次。这就是为什么 React 还会在下次运行效果之前清理上一次渲染中的效果。 ”
2021-05-30 03:30:51
必须对此投反对票,因为您引用的声明没有错,只是不完整。它也来自官方文档,所以暗示他们读错了是没有帮助的。
2021-06-12 03:30:51
那句话是“当组件卸载时,React 会执行清理工作。” 直接来自 React:reactjs.org/docs/hooks-effect.html
2021-06-16 03:30:51

React 文档对此有一个解释部分

简而言之,原因是这样的设计可以防止陈旧数据和更新错误。

useEffectReact 中钩子旨在处理初始渲染和任何后续渲染(这里有更多关于它的信息)。


效果是通过它们的依赖关系控制的,而不是由使用它们的组件的生命周期控制。

任何时候效果的依赖关系发生变化,useEffect都会清除以前的效果并运行新的效果。

这样的设计更具可预测性——每个渲染都有自己独立(纯)的行为效果这确保 UI 始终显示正确的数据(因为 React 心智模型中的 UI 是特定渲染状态的屏幕截图)。

我们控制效果的方式是通过它们的依赖关系。

为了防止在每次渲染时运行清理,我们只需要不要更改效果的依赖项。

具体而言,在您的情况下,清理正在发生,因为array正在发生变化,即Object.is(oldArray, newArray) === false

useEffect(() => {
  // ...
}, [array]);
//  ^^^^^ you're changing the dependency of the effect

您通过以下行导致此更改:

useEffect(() => {
  const id = setInterval(() => {
    setArray(array.concat("hello")); // <-- changing the array changes the effect dep
  }, 3000);
  myRef.current = id;

  return () => {
    clearInterval(myRef.current);
  };
}, [array]); // <-- the array is the effect dep
那么这取决于您希望它何时运行?也许setTimeout这就是你要找的?
2021-06-10 03:30:51
您好,感谢您的回复。这就清楚了。但是,如果我不将 设置array为依赖项,即我将其保留为空数组,setInterval则将只运行一次。你知道如何解决这个问题吗?
2021-06-13 03:30:51

查看代码我可以猜到它是因为第二个 param [array]您正在更新它,因此它将调用重新渲染。尝试设置一个空数组。

每次状态更新都会调用重新渲染和卸载,并且该数组正在发生变化。

我试图设置一个空数组,但setInterval我只会运行一次
2021-05-30 03:30:51
有一篇关于用钩子处理 setInterval 的长篇文章,这不是一项普通的任务:) 这可能会帮助你overreacted.io/making-setinterval-declarative-with-react-hooks
2021-06-17 03:30:51

似乎在意料之中。根据此处的文档, useEffect在首次渲染、每次更新和卸载后调用。

https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects

小费

如果您熟悉 React 类的生命周期方法,您可以将 useEffect Hook 视为 componentDidMount、componentDidUpdate 和之前的 componentWillUnmount 组合。

正如其他人所说,useEffect 取决于 useEffect 中第二个参数中指定的“array”的变化。因此,通过将其设置为空数组,这将有助于在组件安装时触发一次 useEffect。

这里的技巧是改变数组的先前状态。

setArray((arr) => arr.concat("hello"));

见下文:

  useEffect(() => {
     const id = setInterval(() => {
         setArray((arr) => arr.concat("hello"));
     }, 3000);
     myRef.current = id;
     return () => {
        console.log("unmount");
        clearInterval(myRef.current);
     };
  }, []);

我分叉了你的 CodeSandbox 进行演示:https ://codesandbox.io/s/heuristic-maxwell-gcuf7?file =/ src/index.js