随着 React hooks 越来越普遍,项目代码中经常充斥着大量带有依赖数组 deps 的 useEffect 钩子,因此对其执行时机的精准把握至关重要,于是对 useEffect 依赖数组的若干问题进行了详细探究和验证。
一、变化的判断方式
React 官方文档上说依赖数组“改变”才会触发执行,那么怎样才算改变?JS 中判断值相等常用三种,三等号、浅比较和深比较,首先应该排除深比较,因为 JS 对象非常灵活,定义深比较下的相等就不是一件容易的事,深层遍历还可能影响性能;鉴于在 React PureComponent 中用了浅比较,那依赖数组中使用浅比较也不是不可能,所以写个代码验证下:
import { useEffect, useRef, useState } from 'react';
function A ({a}) {
useEffect(() => {
console.log('changed: ', a.b)
}, [a.b])
// window.__ff = (i) => a.b = i; // 局部修改,不触发 useEffect
return <h1>{JSON.stringify(a)}</h1>
}
function App() {
const [t, st] = useState(0)
const p = useRef({a: {b: {c: 0}}})
window.__f = () => st(t => t + 1);
p.current.a = {b: {c : t }}; // a 变化,触发 useEffect
// p.current.a.b = {c : t}; // a.b 变化,触发 useEffect
// p.current.a.b.c = t // a.b.c 变化,不触发 useEffect
return (
<div className="App">
<A a={p.current.a}/>
</div>
);
}
export default App;
- a 变化时,在控制台下调用 __f(),输出 "changed" ;
- a.b 变化时, 在控制台下调用 __f(),输出 "changed";
- a.b.c 变化时, 在控制台下调用 __f(),不输出 "changed";
说明 依赖数组变化的判断方式是三等号。
二、变化是否一定执行
依赖数组变化后,传给 useEffect 的函数是否一定执行呢?答案是 否定的。从 文档 的
默认情况下,effect 会在每轮组件渲染完成后执行
的描述可以看出,函数执行的触发动作是渲染完成,而不是依赖数组改变;依赖数组改变只不是触发后的一个前置判断而已。
在上述代码中,取消注释 window.__ff 函数并在控制台下执行, 可以看到 不输出 "changed"。
三、理解 useEffect
useEffect 应该就如字面意思去理解:是 在组件每次渲染完成后、额外执行的一些操作(副作用);在实际中,有些操作不必每次渲染都执行,所以才通过依赖数组加以优化。
因此 依赖数组和响应式中的 watcher 有本质不同,不可混用。