前言
useEffect
是React中常用的Hook,但是最近在review代码的时候发现了一些useEffect
的滥用。于是学习了React官网教程中的这一篇文章。在这篇文章里列举了许多中错误的useEffect
的用法以及正确的修改方式,在这里简单记录一下在自己代码中高频出现的2个问题。
处理渲染的数据
第一种问题是在useEffect
中处理prop或者state的数据。在渲染前往往需要对数据进行一些计算或者处理,但是在useEffect
中对数据做处理往往是是低效的。
因为useEffect
的执行时机是在组件每一次渲染结束后,也就是说此时真实DOM元素已经被更新了,如果再次对数据进行计算则会再次触发渲染,前面的渲染相当于是多余的,因为它立即就被更新了。
正确的做法是将数据处理放在渲染的过程中进行。
数据变形
比如需要通过拼接firstName
和lastName
来渲染完整的姓名,不推荐的方式是在useEffect
中处理。
❌ 避免在useEffect
中处理state或者prop
js
const Comp = () => {
const [firstName, setFirstName] = useState('Sigmund');
const [lastName, setLastName] = useState('Freud');
useEffect(() => {
setFullName(firstName + '·' + lastName);
}, [firstName, lastName])
// ...
}
✅ 推荐的做法是在渲染时处理。
js
const Comp = () => {
const [firstName, setFirstName] = useState('Sigmund');
const [lastName, setLastName] = useState('Freud');
const fullName = firstName + '·' + lastName;
// ...
}
缓存复杂计算
比如在对输入的列表进行遍历操作(比如过滤)时可能会消耗较长的时间。我们可能会想到利用useEffect
实现数据的缓存,但是这样依然无法避免重复渲染带来的性能问题。
❌ 在useEffect
中处理数据会造成额外渲染。
js
const Comp = ({list}) => {
useEffect(() => {
setFilteredList(getFilteredList(list, filter));
}, [filter])
// ...
}
✅ 同样的,推荐的做法是在渲染时处理。
js
const Comp = ({list}) => {
const filteredList = getFilteredList(list, filter);
// ...
}
🟢 如果需要进行缓存复杂的计算,推荐使用useMemo
。
js
const Comp = ({list}) => {
const filteredList = useMemo(getFilteredList(list, filter), [list]);
// ...
}
响应事件
第二类问题就是在useEffect
中处理事件的响应。useEffect
本质上是提供了一个对这个组件以外的系统产生副作用的能力。
但是并不是所有 的副作用都应该通过useEffect
来产生。useEffect
只应该执行由本组件自身的行为导致的副作用,而不是通过外部事件导致的。
什么是组件自身行为?比如组件的挂载,销毁等都是组件自身的行为。而外部事件可以是用户的操作比如点击提交按钮,输入文字等。(关于这部分可以参考React文档的这一篇文章)
对于外部事件的处理放在useEffect
中会有以下几个问题:
- 产生异步问题。还是由于
useEffect
执行时机问题,在触发事件后,处理事件的回调函数如果在useEffect
中则需要等到渲染结束后执行。这时如果有多个事件产生,执行的顺序可能和触发的顺序不一致。 - 重复的调用。如果组件重新挂载,
useEffect
可能会重复执行,在React的开发模式下,useEffect
会默认执行两次,这样可以帮助开发者发现此类问题。
❌ 不推荐在useEffect
中处理input改变事件。
js
const Comp = ({onChange}) => {
useEffect(() => {
onChange(inputText);
}, [inputText])
// ...
<TextInput onChangeText={(text) => setInputText(text)} />
}
✅ 推荐在使用单独的处理函数处理input改变事件。
js
const Comp = ({onChange}) => {
const handleTextChange = (text) => {
setInputText(text);
onChange(text);
}
// ...
<TextInput onChangeText={handleTextChange} />
}
什么时候应该用useEffect
既然这些useEffect
都是不必要的,那么何时需要使用useEffect
呢?文档中给出的答案是:
You do need Effects to synchronize with external systems.
你只有在同步外部系统的时候需要Effects。
看起来有点难以理解,我目前的理解是原本可以放在lifeCycle函数中执行的,也可以放在useEffect
中。比如组件挂载时的初始化操作,卸载时的清理操作等。
还有就是由于props改变,需要发送请求或者执行某些代码时,可以通过useEffect
执行。
至于state的变化貌似都可以归结于某些事件的触发造成的,因此都可以在单独的eventHandler中处理。