为什么在每个渲染器上都调用`useEffect`中的清理函数?

时间:2019-07-13 21:46:37

标签: javascript reactjs react-hooks use-effect

我一直在学习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 个答案:

答案 0 :(得分:1)

  

React在组件卸载时执行清理。

我不确定您在哪里阅读此书,但此说法不正确。 React performs the cleanup when the dependencies to that hook changes and the effect hook needs to run again with new values。此行为是为了维持视图对更改数据的反应性。以官方示例为例,假设某个应用程序订阅了朋友个人资料中的状态更新。作为您的好朋友,您决定不与他们成为朋友,并与其他人成为朋友。现在,该应用程序需要退订上一个朋友的状态更新,并收听您新朋友的更新。通过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);
    };
  }, []);

由于创建清理函数时该值在(捕获)时已关闭(捕获),因此一些其他反馈将是直接在clearInterval中使用id。无需将其保存到参考。

答案 1 :(得分:0)

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

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

答案 2 :(得分:0)

React文档对此有一个explanation section

简而言之,原因是因为这种设计可以防止过时的数据和更新错误。

React中的useEffect挂钩旨在处理初始渲染和任何后续渲染(here's more about it)。


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

效果更改的任何时间依赖性,useEffect都会清除先前的效果并运行新的效果。

这种设计更具可预测性-each render has its own independent (pure) behavioral effect。这样可以确保用户界面始终显示正确的数据(因为React心理模型中的用户界面是特定渲染状态的屏幕截图)。

我们控制效果的方法是通过它们的依赖性。

为防止清理在每个渲染器上运行,我们只需不必更改效果的依赖性。

具体来说,清理工作是因为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

答案 3 :(得分:0)

似乎是预期的。根据此处的文档,useAffects在首次渲染,每次更新和卸载后调用。

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

  

提示

     

如果您熟悉React类的生命周期方法,可以考虑   useEffect Hook作为componentDidMount,componentDidUpdate和之前的版本   componentWillUnmount组合。

答案 4 :(得分:0)

正如其他人所说,useEffect取决于useEffect的第二个参数中指定的“数组”的更改。因此,通过将其设置为空数组,这将有助于在安装组件时触发一次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