1. 必须完整阅读一次React Hooks官方文档
另外推荐阅读:
Dan的《useEffect完全指南》
衍良同学的《React Hooks完全上手指南》
2. 工程必须引入lint插件,并开启相应规则
lint插件:
www.npmjs.com/package/esl…
必开规则:
{
"plugins": ["react-hooks"],
"rules": {
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn"
}
}
其中,react-hooks/exhaustive-deps少warn,也可以是error。建议全新的工程直接配"error",历史工程配"warn"。
切记,本条是硬性条件。
如果你的工程,当前没开启hooks lint rule,请不要编写任何hooks代码。如果你CR代码时,发现对方前端工程,没有开启相应规则,并且提交了hooks代码,请不要合并。该要求适应于任何一个React前端工程。
这两条规则会避免我们踩坑。虽然对于hooks新手,这个过程可能会比较“痛苦”。不过,如果你觉得这两个规则对你编写代码造成了困扰,那说明你还未完全掌握hooks。
如果对于某些场景,确实不需要「exhaustive-deps」,可在代码处加:
// eslint-disable-next-line react-hooks/exhaustive-deps
切记只能禁本处代码,不能偷懒把整个文件都禁了。
3. 如若有发现hooks相关lint导致的warning,不要全局autofix
除了hooks外,正常的lint基本不会改变代码逻辑,只是调整编写规范。但是hooks的lint规则不同,exhaustive-deps 的变化会导致代码逻辑发生变化,这极容易引发线上问题,所以对于hooks的waning,请不要做全局autofix操作。除非保证每处逻辑都做到了充分回归。 另外公司内部有个小姐姐补充道:eslint-plugin-react-hooks 从2.4.0版本开始,已经取消了 exhaustive-deps 的autofix。所以,请尽量升级工程的lint插件至最新版,减少出错风险。 然后建议开启vscode的「autofix on save」。未来无论是什么问题,能把error与warning 尽量遏制在最开始的开发阶段,保证自测跟测试时就是符合规则的代码。
常见注意点
依赖与闭包问题是一定要开启exhaustive-deps
的核心原因
最常见的错误即:mount时绑定事件,后续状态更新出错。
错误代码示例:
(此处用addEventListener做onclick绑定,只是为了方便说明情况)
function ErrorDemo() {
const [count, setCount] = useState(0);
const dom = useRef(null);
useEffect(() => {
dom.current.addEventListener('click', () => setCount(count + 1));
}, []);
return
div ref={dom}>{count}/div;
}
这段代码的初始想法是:每当用户点击dom,count就加1。理想中的效果是一直点,一直加。但实际效果是 {count} 到「1」以后就加不上了。
我们来梳理一下, useEffect(fn, [1]) 代表只会在mount时触发。也即是首次render时,fn执行一次,绑定了点击事件,点击触发 setCount(count + 1) 。乍一想,count还是那个count,肯定会一直加上去呀,当然现实在啪啪打脸。
状态变更 触发 页面渲染的本质是什么?本质就是 ui = fn(props, state, context) 。props、内部状态、上下文的变更,都会导致渲染函数(此处就是ErrorDemo)的重新执行,然后返回新的view。
那现在问题来了, ErrorDemo 这个函数执行了多次,第一次函数内部的 count 跟后面几次的 count 会有关系吗?这么一想,感觉又应该没有关系了。那为什么 第二次又知道 count 是1,而不是0了呢?第一次的 setCount 跟后面的是同一个函数吗?这背后涉及到hooks的一些底层原理,也关系到了为什么hooks的声明需要声明在函数顶部,不允许在条件语句中声明。在这里就不多讲了。
结论是:每次 count 都是重新声明的变量,指向一个全新的数据;每次的 setCount 虽然是重新声明的,但指向的是同一个引用。
回到正题,我们知道了每次render,内部的count其实都是全新的一个变量。那我们绑定的点击事件方法,也即:setCount(count + 1) ,这里的count,其实指的一直是首次render时的那个count,所以一直是0 ,因此 setCount,一直是设置count为1。
那这个问题怎么解?
首先,应该遵守前面的硬性要求,必须要加lint规则,并开启autofix on save。然后就会发现,其实这个 effect 是依赖 count 的。autofix 会帮你自动补上依赖,代码变成这样:
useEffect(() => {
dom.current.addEventListener('click', () => setCount(count + 1));
}, [count]);
那这样肯定就不对了,相当于每次count变化,都会重新绑定一次事件。