三种常用的生命周期方法与useEffect

三种常用的生命周期方法与useEffect

Content #

在类组件中,componentDidMount,componentWillUnmount,和 componentDidUpdate 这三个生命周期方法可以说是日常开发最常用的。业务逻辑通常要分散到不同的生命周期方法中,比如说在上面的 Blog 文章的例子中,你需要同时在 componentDidMount 和 componentDidUpdate 中去获取数据。

而在函数组件中,这几个生命周期方法可以统一到 useEffect 这个 Hook,正如 useEffect 的字面含义,它的作用就是触发一个副作用,即在组件每次 render 之后去执行。

下面的代码演示了这三个生命周期方法是如何用 useEffect 实现的:

useEffect(() => {
  // componentDidMount + componentDidUpdate
  console.log('这里基本等价于 componentDidMount + componentDidUpdate');
  return () => {
    // componentWillUnmount
    console.log('这里基本等价于 componentWillUnmount');
  }
}, [deps])

可以看到,useEffect 接收的 callback 参数,可以返回一个用于清理资源的函数,从而在下一次同样的 Effect 被执行之前被调用。

你可能已经注意到了,在代码里我用了“基本等价于”这个说法,什么意思呢?指的就是这个写法并没有完全等价于传统的这几个生命周期方法。主要有两个原因。

一方面,useEffect(callback) 这个 Hook 接收的 callback,只有在依赖项变化时才被执行。而传统的 componentDidUpdate 则一定会执行。这样来看,Hook 的机制其实更具有语义化,因为过去在 componentDidUpdate 中,我们通常都需要手动判断某个状态是否发生变化,然后再执行特定的逻辑。

另一方面,callback 返回的函数(一般用于清理工作)在下一次依赖项发生变化以及组件销毁之前执行,而传统的 componentWillUnmount 只在组件销毁时才会执行。

第二点可能有点难理解,我们不妨继续看博客文章这个例子。假设当文章 id 发生变化时,我们不仅需要获取文章,同时还要监听某个事件,这样在有新的评论时获得通知,就能显示新的评论了。这时候的代码结构如下:

import React, { useEffect } from 'react';
import comments from './comments';

function BlogView({ id }) {
  const handleCommentsChange = useCallback(() => {
    // 处理评论变化的通知
  }, []);
  useEffect(() => {
    // 获取博客内容
    fetchBlog(id);
    // 监听指定 id 的博客文章的评论变化通知
    const listener = comments.addListener(id, handleCommentsChange);

    return () => {
      // 当 id 发生变化时,移除之前的监听
      comments.removeListener(listener);
    };
  }, [id, handleCommentsChange])
}

那么可以比较清楚地看到,useEffect 接收的返回值是一个回调函数,这个回调函数不只是会在组件销毁时执行,而且是每次 Effect 重新执行之前都会执行,用于清理上一次 Effect 的执行结果。

理解这一点非常重要。useEffect 中返回的回调函数,只是清理当前执行的 Effect 本身。这其实是更加语义化的,因此你不用将其映射到 componentWillUnmount,它也完全不等价于 componentWillUnmount。你只需记住它的作用就是用于清理上一次 Effect 的结果就行了,这样在实际的开发中才能够使用得更加自然和合理。

Viewpoints #

From #

05|进一步认识 Hooks :如何正确理解函数组件的生命周期?