React useEffect 用户指南
useEffect 介绍
useEffect 是在 React 文档中介绍的第二个 React hook。
从命名可知,与 Effect 副作用有关,那么什么是副作用呢?
在 《mostly adequate guide》一书中,是这样定义的:
A side effect is a change of system state or observable interaction with the outside world that occurs during the calculation of a result.
对应的中文意思是:副作用是在计算结果的过程中,系统状态的一种变化,或者与外部世界进行的 可观察的交互。
产生副作用的情形:修改 DOM、发送 http 请求、修改 URL query、修改 cookie、sessionStorage、localStorage 和 console.log
等。可见,副作用无法避免,而且非常常见,useEffect 可以让我们在函数组件中执行副作用操作。
useEffect 的参数: 我们查看 useEffect 的声明, 在 react/index.d.ts 可以看到 useEffect
的第一个参数是 effect 的回调,第二个参数是 deps 依赖项,可选,类型是数组,会根据依赖项,决定是否调用 EffectCallback。
useEffect 调用时机:
- 与渲染的先后关系:React 会等待浏览器完成画面渲染之后才会延迟调用 useEffect
- 调用次数:在不传 deps 依赖项情况下,useEffect 会在每次渲染后都执行
useEffect 依赖项
接下来会介绍 通过控制依赖项,有条件地执行 EffectCallback,可用于性能优化。
一、不传依赖项
调用时机: 在不传 deps 依赖项情况下,useEffect 会在每次渲染后都执行,即它在第一次渲染之后和每次更新之后都会执行。相当于在 componentDidMount
和 componentDidUpdate
两个类方法中执行。
适合于 effect 的回调里计算量小,不会触发渲染的情形。
为降低开发难度,React 文档推荐这种使用方式,可点击查看 解释: 为什么每次更新的时候都要运行 Effect
示例代码如下:
二、依赖项为空数组
调用时机: 当不需要在每次渲染后都执行,希望只在它第一次渲染之后执行,我们可以把 useEffect 的依赖项设为空数组,相当于以前的 class 组件的 componentDidMount
。
以下举三个例子
-
引用官方文档的例子,设定 Interval 定时器。
这个例子需求是让 state count 每秒自加一。如果不传依赖项,就会每秒都
setInterval
, 不符合需求。正确做法是,只在第一次渲染之后执行。代码如下: -
进行数据请求。
-
从 URLSearchParams 获取数据,用于初始化 state。
这个例子是在页码选择器,从 URL query 中获取 page,用于初始化 page state。
三、依赖项为非空数组
调用时机: 当依赖项为非空数组,则 在每次渲染后,非空数组上的 state 有更新时,就会执行 EffectCallback,即 useEffect 传入回调函数。
前文提到了 “当依赖项为非空数组,则在每次渲染后,非空数组上的 state 有更新时,就会执行 EffectCallback ”
这里的更新是指什么呢?是指对依赖项进行浅比较,对数组中的每个state,进行上次渲染后的值,跟这次渲染后的值,进行浅比较,不相等时,就执行 EffectCallback。
浅比较如何比较呢?
在 JavaScript 中,数据类型分为 值类型 和 引用类型。
对于值类型的浅比较是对比值是否相等。
有 boolean number string 三种,比如 true !== false
、1 !== 2
和 '1' !== '2'
。
对于引用类型的浅比较是对比对象的引用,对比对象指向的内存地址。有 object array function 等类型。
useEffect 清除机制
触发时机: 在依赖项为空数组下,相当于 class 组件的 componentWillUnmount 。
我们查看 react/index.d.ts#L902,可以发现 useEffet 回调函数 EffectCallback 的返回值 void | Destructor
,其中 Destructor
是可选的析构函数。
type EffectCallback = () => (void | Destructor);
我们再重新看上面计数器的例子,实际上在组件销毁时,还需要清除计时器 clearInterval
,可通过在 EffectCallback 返回 () => clearInterval(id)
去实现。
更准确的触发时机: 当依赖项更新时,先调用上一次渲染后执行的 EffectCallback 的销毁函数
代码示例:我们继续复用计数器的例子,将每秒自加一,改为由参数配置每隔几秒自加一,修改代码位置如下:
可以在 codepen 上尝试本代码 的实际运行效果 ????
参考资料
- React 文档
- 极客时间 -《React Hooks 核心原理与实战》