关于useEffect的理解

1,542 阅读3分钟

React16.8新增的useEffec这个hook函数就是处理副作用的。\

所谓的“副作用”,举个通俗一点的例子,假如感冒了本来吃点药就没事了,但是吃了药发现身体过敏了,而这个“过敏”就是副作用。

放到React中,本来只是想渲染DOM展示到页面上,但除了DOM之外还有数据,而这些数据必须从外部的数据源中获取,这个“获取外部数据源”的过程就是副作用。

useEffect怎么用可以参考官网给出的例子,这里主要针对使用useEffect过程中遇到的问题进行总结。

避免重复循环渲染

利用useEffect接收一个数组作为第二个参数,将第二个参数作为dependence,每次渲染完DOM执行副作用函数时都会浅比较dependence渲染前后的值是否一致,不一致就执行副作用,反之就不执行;如果该dependence为一个空数组[],即没有传入比较变化的变量,则比较结果永远都保持不变,那么副作用逻辑就只能执行一次。

useEffect(() => {
  setTimeout(() => {    
        setCounter(counter + 1);  
    }, 300)
}, []);

初此之外,如果我们还想通过点击刷新按钮实现获取外部数据但又不想造成死循环,那么可以通过一个变量作为“开关”,在实现目的的同时做到避免循环渲染DOM。

画动图太麻烦,各位看注释脑补😂

function App() {  
    const [count, setCount] = useState(0);  
    const [loading, setLoading] = useState(true); // loading作为开关  
    useEffect(() => {    
        if (loading) { // 注意这里只有当loading为true时才执行      
            setTimeout(() => {        
                setCount(count + 1);        
                setLoading(!loading); // 改变loading值      
            });    
        }  
  }, [loading]); // loading在这里作为dependence
  
  // 第一次DOM渲染完成后,loading为true,执行副作用函数,count值变为1,loading变为false,由于
  // 改变了state的值,会update,组件会再次render,但此时loading为false,不会执行setTimeout,
  // 避免了循环

  // 当点击Refresh刷新,loading由上一次的false变为了true,函数执行一次update
  // DOM更新完后执行useEffect,因为loading已经为true了,所以副作用函数可执行,count从1变为2,
  // loading又从true变为false,就这样交替进行。。。
  return (    
      <div>      
          <h3>{count}</h3>      
          <button onClick={() => {setLoading(true);}}>Refresh</button>
      </div>  
     );
 }

关于副作用的清除

useEffect可以返回一个函数来作为清除副作用。

  useEffect(() => {    
    ChatAPI.subscribeToFriendStatus(props.id, handleStatusChange);  
    function clear(){
        ChatAPI.unsubscribeFromFriendStatus(props.id, handleStatusChange);
    }  
    return clear;
});

这里会涉及到useEffect执行和销毁的过程:

  1. 传入props.id = 1
  2. 组件渲染
  3. DOM渲染完成,执行副作用函数,返回清除副作用函数clear,命名为clear1
  4. 传入props.id=2
  5. 组件渲染
  6. DOM渲染完成,执行clear1
  7. 副作用函数执行并返回另一个clear函数,命名为clear2
  8. 组件销毁,clear2执行

由此可推测出副作用清除函数的特征:

  • 每次副作用执行都会返回一个清除函数
  • 清除函数会在下一次副作用函数执行之前(DOM渲染完成之后)执行
  • 组件销毁也会执行一次清除函数

从打印出的count值也可以看出,清除函数会在下一次副作用函数执行之前执行,即在清除函数里的count值是上一次缓存的count值:

进一步思考,clear1执行的时候,访问了props.id,那么这个id值是1还是2呢?\

这里就涉及到闭包的知识概念了,因为useEffect返回的是个函数,在执行时产生了一个闭包,根据闭包的相关定义,返回的clear函数能访问自身作用域外的变量,当组件第一次渲染时传入id=1,此时的clear函数中的props.id值为1。