useEffect 中是否可以有条件地进行不同的清理?

Can different cleanups be done conditionally in useEffect?

所以,我有一个 useEffect 这样的:

useEffect(()=>{
    if(foo) {
        // do something
        return () => { // cleanup function }
    }
}, [foo])

此处,即使执行了 if 块,也不会调用 cleanup 函数。但是如果我修改效果为:

useEffect(()=>{
    if(foo) {
        // do something
    }
    return () => { // cleanup function }
}, [foo])

有效。 那么,是否仅当 returnuseEffect 的最后一条语句时才进行清理,还是我遗漏了什么?

当您 return 将回调中的函数传递给 useEffect 时,returned 函数将在组件从 UI 中删除之前被调用。

通常我们会在 componentWillUnmount 中对基于 class 的组件进行清理。假设您想在 componentDidMount 上创建一个事件侦听器并在 componentDidUnmount 上清理它。就像 class 基于钩子的代码将合并到一个回调函数中。

对于清理定义这样的例子

useEffect(()=>{
        let interval = setInterval(()=>{
           // do something 
         }),1000}
    return () => { clearInterval(interval) }
}, [foo])

为了更好地理解检查官方 Effects with Cleanup

每次调用 useEffect 更新程序函数时都会重新创建清理函数。当依赖关系发生变化时(如果没有依赖关系,则在每次渲染时)或卸载组件之前,将调用当前清理函数。调用cleanup之后,updater会被调用,可以returned一个新的cleanup函数。

您可以 return 一个不同的函数,或者根本 none,无论何时调用更新程序函数。

比如多次点击Inc按钮,可以在控制台看到清理功能只对偶数counter存在,因为它是return有条件地编辑。

const { useState, useEffect } = React;

const Demo = () => {
  const [counter, setCount] = useState(0);
  
  useEffect(() => {
    console.log(`Effect ${counter}`);
    
    if(counter % 2 === 0) {
      return () => console.log(`Cleanup ${counter}`);
    }
  }, [counter]);
  
  return (
    <div>
      <p>Counter: {counter}</p>
      
      <button onClick={() => setCount(counter + 1)}>Inc</button>
    </div>
  );
};

ReactDOM.render(
  <Demo />,
  root
);
.as-console-wrapper { top: 0; left: 50% !important; max-height: unset !important; }
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<div id="root"></div>

有时,将清除函数嵌套在条件中可能会造成混淆。另一种选择是始终 return 清理函数,并将逻辑放入其中:

useEffect(()=>{
    if(foo) {
        // do something
    }
    
    return () => { 
      if(foo) {
        // cleanup something
      }
    }
}, [foo])

你可以在这个例子中看到,结果是一样的:

const { useState, useEffect } = React;

const Demo = () => {
  const [counter, setCount] = useState(0);
  
  useEffect(() => {
    console.log(`Effect ${counter}`);

    return () => {
      if(counter % 2 === 0) {
        console.log(`Cleanup ${counter}`);
      }    
    };
  }, [counter]);
  
  return (
    <div>
      <p>Counter: {counter}</p>
      
      <button onClick={() => setCount(counter + 1)}>Inc</button>
    </div>
  );
};

ReactDOM.render(
  <Demo />,
  root
);
.as-console-wrapper { top: 0; left: 50% !important; max-height: unset !important; }
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<div id="root"></div>