一文搞定 React 的 useEffect

当依赖为引用数据类型时

依赖是引用数据类型和基本类型对比

不同类型对比

每次点击发现只会输出 useEffect - obj,也就是当 useEffect 依赖为对象时,每次更新都会执行。
控制台输出

依赖是如何进行比较的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function areHookInputsEqual(
nextDeps: Array<mixed>,
prevDeps: Array<mixed> | null,
): boolean {
if (prevDeps === null) {
return false;
}
for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
if (objectIs(nextDeps[i], prevDeps[i])) {
continue;
}
return false;
}
return true;
}

比较分为三种方式:

  1. 当 prevDeps 为 null ,也就是没传入依赖(在这之前会对依赖进行处理,如果传入依赖为 undefined 会赋值 null),始终返回 false,每次都执行
  2. 如果传入依赖就进行遍历,通过 Object.is 进行比较,当比较出不等时返回 false,也就是依赖发生变化时执行
  3. 遍历结束认为依赖没发生变化,返回 true,不执行

解决方案

上文分析了问题产生的原因,那么应该如何处理呢?

使用 useRef

使用useRef

因为 ref 会单独开辟一个地址,在组件更新前后不会新建,这也是能够用 useRef 进行缓存的原因。这里我们使用 useRef 包裹一个对象,每次对比的都是相同的对象,所以 useEffect 只会在初始化执行,更新时不会执行。

useEffect 返回函数 cleanup

执行时机

cleanup 会在组件销毁、依赖改变的情况下执行。

当改变 num 值和控制 Child 组件隐藏销毁组件时,都会有输出。

清除副作用

cleanup 函数通常用来清除副作用,例如清理定时器,清除事件监听等,这里用计时器作为例子:

计时

这段代码中我们就需要每次清空循环定时器才能达到想要的效果,不然每次更新都会创建循环定时器。

useEffect 模拟声明周期

React hooks 提供 useEffect、useLayoutEffect 来解决函数式组件没有声明周期的问题。

componentDidMount

1
2
3
useEffect(() => {
// ...
}, [])

当 useEffect 依赖为 [] 时,setup 函数也就是 useEffect 的第一个参数,只在组件初始化时执行。

componentWillUnmount

1
2
3
useEffect(() => () => {
// ...
}, [])

在上一个的基础上,setup 函数的返回函数 cleanup 函数会在每次依赖项变更重新渲染后,用旧的值执行一遍,当组件从页面卸载也会执行一遍。一般在这里清除定时器,关闭事件监听等等。

componentWillReceiveProps

1
2
3
useEffect(() => {
// ...
}, [props])

将 props 放在 useEffect 的依赖中,会在 props 变化时执行。和 componentWillReceiveProps 区别在于 useEffect 在 commit 阶段才会执行,componentWillReceiveProps 在 render 阶段就执行,另外 useEffect 在初始化时默认执行一次。

componentDidUpdate

1
2
3
useEffect(() => {
// ...
})

不设置 useEffect 的依赖,将会在组件状态更新时执行。和 componentDidUpdate 区别在于 useEffect 是异步执行,componentDidUpdate 是同步执行,并且 useEffect 在初始化时默认执行一次。

无限循环

当我们模拟 componentDidUpdate 时,如果在 setup 函数中更新状态,会导致 更新状态 -> 执行 -> 更新状态 -> 执行 ...无限循环执行。

useEffect、useLayoutEffect、useInsertionEffect

useEffect 在浏览器渲染完成之后异步执行,useLayoutEffect 在 DOM 更新后,浏览器渲染前同步执行,会阻塞浏览器绘制。对于 DOM 的操作一般放在 useLayoutEffect 中,因为 useEffect 执行时 DOM 已经渲染完成了,再去修改 DOM 可能会产生回流或者重绘。

React18 版本又提供 useInsertionEffect 来处理 CSS-in-JS 在渲染中注入样式的性能问题。


一文搞定 React 的 useEffect
https://l1ushun.github.io/2024/04/25/useEffect/
作者
liu shun
发布于
2024年4月25日