1. 不是所有的依赖都必须放到依赖数组中
对于所有的 React Hooks 用户,都有一个共识:“useEffect 中使用到外部变量,都应该放到第二个数组参数中”,同时我们会安装 eslint-plugin-react-hooks 插件,来提醒自己是不是忘了某些变量。
以上共识来自官方文档:
https://zh-hans.reactjs.org/docs/hooks-reference.html#useeffect
我愿称该条规则为万恶之源,这条规则以高亮展示,所有的新人都很重视,包括我自己。然而在实际的开发中,发现事情并不是这样的。
下面举一个比较简单的例子,要求如下:当 props.count
和 count
变化时,上报当前所有数据。
这个例子比较简单,先贴下源码:
function Demo(props) {
const [count, setCount] = useState(0);
const [text, setText] = useState('');
const [a, setA] = useState('');
useEffect(() => {
monitor(props.count, count, text, a);
}, [props.count, count]);
return (
<div>
<button
onClick={
() => setCount(c => c + 1)}
>
click
</button>
<input value={
text} onChange={
e => setText(e.target.value)} />
<input value={
a} onChange={
e => setA(e.target.value)} />
</div>
)
}
复制代码
我们能看到示例代码中,useEffect 是不符合 React 官方建议的,text
和 a
变量没有放到依赖数组中,ESLint 警告如下:
那如果按照规范,我们把依赖项都放到第二个数组参数中,会怎样呢?
useEffect(() => {
monitor(props.count, count, text, a);
}, [props.count, count, text, a]);
复制代码
如上的代码虽然符合了 React 官方的规范,但不满足我们的业务需求了,当 text
和 a
变化时,也触发了函数执行。
此时陷入了困境,当满足 useEffect 使用规范时,业务需求就不能满足了。当满足业务需求时,useEffect 就不规范了。
我的建议为:
- 不要使用
eslint-plugin-react-hooks
插件,或者可以选择性忽略该插件的警告。 - 只有一种情况,需要把变量放到 deps 数组中,那就是当该变量变化时,需要触发 useEffect 函数执行。而不是因为 useEffect 中用到了这个变量!
2. deps 参数不能缓解闭包问题
假如完全按第二个建议来写代码,很多人又担心,会不会造成一些不必要的闭包问题?我的结论是:闭包问题和 useEffect 的 deps 参数没有太大关系。
比如我有一个这样的需求:当进入页面 3s 后,输出当前最新的 count。代码如下:
function Demo() {
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setTimeout(() => {
console.log(count)
}, 3000);
return () => {
clearTimeout(timer);
}
}, [])
return (
<button
onClick={
() => setCount(c => c + 1)}
>
click
</button>
)
}
复制代码
以上代码,实现了初始化 3s 后,输出 count。但很遗憾,这里肯定会出闭包问题,哪怕进来之后我们多次点击了 button,输出的 count 仍然为 0。
那假如我们把 count
放到 deps 中,是不是就好了?
useEffect(() => {
const timer = setTimeout(() => {
console.log(count)
}, 3000);
return () => {
clearTimeout(timer);
}
}, [count])
复制代码
如上代码,此时确实没有闭包问题了,但在每次 count
变化时,定时器卸载并重新开始计时了,不满足我们的最初需求了。
要解决的唯一办法为: