React Hooks技术最佳实践(二)

useEffect

useEffect是除useState之外使用最常用的Hooks之一,它可以用来管理副作用,替代传统class组件中的componentDidMountcomponentWillUnmount方法或是根据依赖项来执行代码。

useEffect的用法并不复杂,但是如果对于它的执行过程和机制不熟悉的话,还是很容易出现死循环、依赖运算错误等问题。同时,它每次运行都相当于对当前的状态存储了一份快照,这就这意味在它当中执行setTimeout获取到的都不是最新的状态,而是运行时的状态。想要更加深入了解的推荐阅读useEffect 完整指南

本文章属于React Hooks技术最佳实践的第二篇文章,第一篇介绍了useState的最佳实践,感兴趣的朋友可以阅读React Hooks技术最佳实践(一)

替代componentDidMount

通过设置useEffect的第二个依赖参数为[]可以替代componentDidMount,虽然它们的执行时机不是完全一致的。这样我们就可以在useEffect中处理需要在组件挂载后的操作或者异步请求,它能够获取到state。因为依赖项为[],所以在整个组件生命周期中只会在挂载时运行。

1
2
3
4
useEffect(() => {
// 在组件挂载后处理事务或副作用
// 能够获取到state
}, [])

副作用

先来看看如何正确的使用async方法。

错误的示范

1
2
3
useEffect(async () => {
const { data, result } = await asyncRequest();
}, [])

useEffect并不建议你在回调函数中直接使用async,这样使用会直接触发报错,通常如果安装了相关lint的话会有提示。因为这么使用会导致回调函数相互之间产生竞争状态,而Effect回调函数应该是同步的。

推荐的做法

1
2
3
4
5
6
7
const requestFn = async () => {
const { data, result } = await asyncRequest();
}

useEffect(() => {
requestFn();
}, [])

如果需要在获取异步数据之后更新state,也可以在requestFn函数中处理。

1
2
3
4
5
6
7
8
9
10
const [value, updateValue] = useState();

const requestFn = async () => {
const { data, result } = await asyncRequest();
updateValue(value);
}

useEffect(() => {
requestFn();
}, [])

如果异步请求依赖任何的state,则需要清晰的写在useEffect的依赖项中,这样每次在state改变之后都会执行异步请求。

1
2
3
4
5
6
7
8
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

错误的写法

1
2
3
4
5
const [count, updateCount] = useState(0);

useEffect(() => {
updateCount(prevCount => prevCount++);
})

正确的写法一

1
2
3
4
5
6
7
8
const [count, updateCount] = useState(0);

// 增加前置条件,满足时才执行更新状态
useEffect(() => {
if (count < 1) {
updateCount(prevCount => prevCount++);
}
})

正确的写法二

1
2
3
4
5
6
7
const [count, updateCount] = useState(0);
const [num, updateNum] = useState(1)

// 依赖num,每次num改变后才会执行Effect
useEffect(() => {
updateCount(prevCount => prevCount+num);
}, [num])

依赖函数

除了使用stateprops作为依赖项,函数也是可以直接作为依赖项使用。不过由于函数每次在组件渲染时都会重新执行,所以Effect会没必要的重复执行。

不过可以使用useCallback方法来避免这种情况。

每次渲染都重复执行Effect

1
2
3
4
5
const doSomething = () => {}

useEffect(() => {
// do somethings
}, [doSomething])

使用useCallback

1
2
3
4
5
const doSomething = () => useCallback(() => {}, [])

useEffect(() => {
// do somethings once
}, [doSomething])

如果函数依赖任何其他的状态执行,则可以将依赖加入到useCallback的依赖数组项中,这样在依赖项改变后函数都会重新执行,Effect由于依赖了函数,所以Effect也会执行。

优化

正确的使用Effect的依赖是非常重要的,每次依赖改变后都会使用Object.is方法来比较,如果不同,则执行Effect。所以我们可以通过Memoization技术来优化,具体来说就是每次状态改变后都会与之前记忆的状态作对比,如果值发生了改变,则返回新的状态,并记忆,如果值没有改变,只是引用变了,则返回记忆的状态。这样Effect只在值改变后才执行。

这一部分在之前的文章中有介绍,这里就不具体展开了。

Memoization技术在React中的应用

useLayoutEffect

大多数情况下使用useEffect即可,不过当你需要在useEffect中操作DOM时,为了优化渲染效果,可以使用useLayoutEffect。它会在DOM更新完成之后再执行,同时可以读取到DOM布局并同步触发渲染,之后浏览器才进行绘制。

下一篇介绍其他的Hooks的用法与技巧。

官方的FAQ其实写的很不错,很多陷阱和技巧都介绍了,推荐阅读。

参考文章

  1. useEffect 完整指南
Author: linxiangjun
Link: http://www.linxiangjun.com/react-hooks-best-practices-2.html
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.