欢迎访问我的博客
原文地址:React Hooks技术最佳实践(二)
useEffect
useEffect
是除useState
之外使用最常用的Hooks之一,它可以用来管理副作用,替代传统class组件中的componentDidMount
和componentWillUnmount
方法或是根据依赖项来执行代码。
useEffect
的用法并不复杂,但是如果对于它的执行过程和机制不熟悉的话,还是很容易出现死循环、依赖运算错误等问题。同时,它每次运行都相当于对当前的状态存储了一份快照,这就这意味在它当中执行setTimeout
获取到的都不是最新的状态,而是运行时的状态。想要更加深入了解的推荐阅读useEffect 完整指南。
本文章属于React Hooks技术最佳实践的第二篇文章,第一篇介绍了useState
的最佳实践,感兴趣的朋友可以阅读React Hooks技术最佳实践(一)。
替代componentDidMount
通过设置useEffect
的第二个依赖参数为[]
可以替代componentDidMount
,虽然它们的执行时机不是完全一致的。这样我们就可以在useEffect
中处理需要在组件挂载后的操作或者异步请求,它能够获取到state
。因为依赖项为[]
,所以在整个组件生命周期中只会在挂载时运行。
useEffect(() => {
// 在组件挂载后处理事务或副作用
// 能够获取到state
}, [])
副作用
先来看看如何正确的使用async
方法。
错误的示范
useEffect(async () => {
const { data, result } = await asyncRequest();
}, [])
useEffect
并不建议你在回调函数中直接使用async
,这样使用会直接触发报错,通常如果安装了相关lint的话会有提示。因为这么使用会导致回调函数相互之间产生竞争状态,而Effect回调函数应该是同步的。
推荐的做法
const requestFn = async () => {
const { data, result } = await asyncRequest();
}
useEffect(() => {
requestFn();
}, [])
如果需要在获取异步数据之后更新state
,也可以在requestFn
函数中处理。
const [value, updateValue] = useState();
const requestFn = async () => {
const { data, result } = await asyncRequest();
updateValue(value);
}
useEffect(() => {
requestFn();
}, [])
如果异步请求依赖任何的state
,则需要清晰的写在useEffect
的依赖项中,这样每次在state
改变之后都会执行异步请求。
const requestFn = async (param) => {
const { data, result } = await asyncRequest(param);
updateValue(value);
}
useEffect(() => {
requestFn(param);
}, [param])
依赖
正确的使用
我们知道每次state
或者props
改变都会导致组件的re-render,所以useEffect
在没有任何依赖项时每次都会执行一遍。这时如果在它当中改变了state
,那么就会导致死循环。过程就是执行useEffect
改变了state
,而改变state
又导致了重复执行useEffect
。
错误的写法
const [count, updateCount] = useState(0);
useEffect(() => {
updateCount(prevCount => prevCount++);
})
正确的写法一
const [count, updateCount] = useState(0);
// 增加前置条件,满足时才执行更新状态
useEffect(() => {
if (count < 1) {
updateCount(prevCount => prevCount++);
}
})
正确的写法二
const [count, updateCount] = useState(0);
const [num, updateNum] = useState(1)
// 依赖num,每次num改变后才会执行Effect
useEffect(() => {
updateCount(prevCount => prevCount+num);
}, [num])
依赖函数
除了使用state
、props
作为依赖项,函数也是可以直接作为依赖项使用。不过由于函数每次在组件渲染时都会重新执行,所以Effect会没必要的重复执行。
不过可以使用useCallback
方法来避免这种情况。
每次渲染都重复执行Effect
const doSomething = () => {}
useEffect(() => {
// do somethings
}, [doSomething])
使用useCallback
const doSomething = () => useCallback(() => {}, [])
useEffect(() => {
// do somethings once
}, [doSomething])
如果函数依赖任何其他的状态执行,则可以将依赖加入到useCallback
的依赖数组项中,这样在依赖项改变后函数都会重新执行,Effect由于依赖了函数,所以Effect也会执行。
优化
正确的使用Effect的依赖是非常重要的,每次依赖改变后都会使用Object.is
方法来比较,如果不同,则执行Effect。所以我们可以通过Memoization技术来优化,具体来说就是每次状态改变后都会与之前记忆的状态作对比,如果值发生了改变,则返回新的状态,并记忆,如果值没有改变,只是引用变了,则返回记忆的状态。这样Effect只在值改变后才执行。
这一部分在之前的文章中有介绍,这里就不具体展开了。
useLayoutEffect
大多数情况下使用useEffect
即可,不过当你需要在useEffect
中操作DOM时,为了优化渲染效果,可以使用useLayoutEffect
。它会在DOM更新完成之后再执行,同时可以读取到DOM布局并同步触发渲染,之后浏览器才进行绘制。
下一篇介绍其他的Hooks的用法与技巧。
官方的FAQ其实写的很不错,很多陷阱和技巧都介绍了,推荐阅读。