1.3.3 useEffect
useEffect是除useState之外另一个常用的Hooks,理解它比理解useState的难度更大,但只要明白函数组件每次渲染都有它自己的状态和props,那么理解useEffect将变得容易。
useEffect能让开发人员知道DOM什么时候被绘制到了屏幕上,组件什么时候被卸载了。有些开发人员认为useEffect是类组件componentDidMount、componentDidUpdate和componentWillUnmount的生命周期函数的结合,但实际上函数组件没有与类组件类似的生命周期概念。useEffect类型定义如下。
从类型定义可以看出,useEffect最多可接收两个参数。第一个参数是函数,可以有返回值,本小节将该函数称为effect;第二个参数是非必填的,是一个数组,它是effect的依赖,称为deps。deps用于确定effect在本次渲染中是否应该执行,若应该执行,则在浏览器中将DOM绘制到屏幕之后执行,可以将Ajax请求、访问DOM等操作放在effect中,它不会阻塞浏览器绘制。
函数组件可以多次使用useEffect,每使用一次就定义一个effect,这些effect的执行顺序与它们被定义的顺序一致,建议将不同职责的代码放在不同的effect中。接下来从effect的清理工作和依赖这两个方面介绍useEffect。
1.effect的清理工作
effect没有清理工作就意味着它没有返回值,相关代码如下。
上述代码定义了一个effect,它的作用是将document.title设置成本次渲染时name的值。
effect的清理工作由effect返回的函数完成,该函数在组件重新渲染后和组件卸载时调用。代码清单1-2定义了一个有清理工作的effect。
代码清单 1-2
上述effect在DOM被绘制到界面之后给body元素绑定click事件,组件重新渲染之后将上一次effect绑定的click事件解绑。该effect在组件首次渲染和之后的每次重新渲染时都会执行,如果组件的状态更新频繁,那么组件重新渲染也会很频繁,这将导致body频繁绑定click事件又解绑click事件。是否有办法使组件只在首次渲染时给body绑定事件呢?当然有,那就是依赖。
2.effect的依赖
前面两个示例定义的effect没有指明依赖,因此组件的每一轮渲染都会执行它们。修改代码清单1-2,让组件只在首次渲染时给body绑定事件,实现代码如下。
给useEffect的第二个参数传空数据意味着effect没有依赖,该effect只在组件初始渲染时执行,它的清理工作在组件卸载时执行。对于绑定DOM事件而言这是一件好事,它可以防止事件反复绑定和解绑,但问题是,如果在事件处理程序中访问组件的状态和props,那么只能拿到它们的初始值,拿不到最新的值。是否有办法让effect始终拿到状态和props最新的值呢?有。
给effect传递依赖项,React会将本次渲染时依赖项的值与上一次渲染时依赖项的值进行浅对比,如果它们当中的一个有变化,那么该effect会被执行,否则不会执行。为了让effect拿到它所需状态和props的最新值,effect中所有要访问的外部变量都应该作为依赖项放在useEffect的第二个参数中。相关代码如下。
上述effect在组件初始渲染时会执行,当name发生变化导致组件重新渲染时也会执行,相应地,组件卸载时和由name的变化导致组件重新渲染之后将清理上一个effect。
注意
函数组件每次渲染时,effect都是一个不同的函数,在函数组件内的每一个位置(包括事件处理函数、effects、定时器等)只能拿到定义它们的那次渲染的状态和props。