你可能并不需要useEffect

前言

useEffect是React中常用的Hook,但是最近在review代码的时候发现了一些useEffect的滥用。于是学习了React官网教程中的这一篇文章。在这篇文章里列举了许多中错误的useEffect的用法以及正确的修改方式,在这里简单记录一下在自己代码中高频出现的2个问题。

处理渲染的数据

第一种问题是在useEffect中处理prop或者state的数据。在渲染前往往需要对数据进行一些计算或者处理,但是在useEffect中对数据做处理往往是是低效的。

因为useEffect的执行时机是在组件每一次渲染结束后,也就是说此时真实DOM元素已经被更新了,如果再次对数据进行计算则会再次触发渲染,前面的渲染相当于是多余的,因为它立即就被更新了。

正确的做法是将数据处理放在渲染的过程中进行。

数据变形

比如需要通过拼接firstNamelastName来渲染完整的姓名,不推荐的方式是在useEffect中处理。

❌ 避免在useEffect中处理state或者prop

const Comp = () => {
    const [firstName, setFirstName] = useState('Sigmund');  
    const [lastName, setLastName] = useState('Freud');
    
    useEffect(() => {
        setFullName(firstName + '·' + lastName);
    }, [firstName, lastName])
    // ...
}

✅ 推荐的做法是在渲染时处理。

const Comp = () => {
    const [firstName, setFirstName] = useState('Sigmund');  
    const [lastName, setLastName] = useState('Freud');
    const fullName = firstName + '·' + lastName;
    // ...
}

缓存复杂计算

比如在对输入的列表进行遍历操作(比如过滤)时可能会消耗较长的时间。我们可能会想到利用useEffect实现数据的缓存,但是这样依然无法避免重复渲染带来的性能问题。

❌ 在useEffect中处理数据会造成额外渲染。

const Comp = ({list}) => {
    useEffect(() => {
       setFilteredList(getFilteredList(list, filter));
    }, [filter])
    // ...
}

✅ 同样的,推荐的做法是在渲染时处理。

const Comp = ({list}) => {
    const filteredList = getFilteredList(list, filter);
    // ...
}

🟢 如果需要进行缓存复杂的计算,推荐使用useMemo

const Comp = ({list}) => {
    const filteredList = useMemo(getFilteredList(list, filter), [list]);
    // ...
}

响应事件

第二类问题就是在useEffect中处理事件的响应。useEffect本质上是提供了一个对这个组件以外的系统产生副作用的能力。

但是并不是所有的副作用都应该通过useEffect来产生。useEffect只应该执行由本组件自身的行为导致的副作用,而不是通过外部事件导致的。

什么是组件自身行为?比如组件的挂载,销毁等都是组件自身的行为。而外部事件可以是用户的操作比如点击提交按钮,输入文字等。(关于这部分可以参考React文档的这一篇文章

对于外部事件的处理放在useEffect中会有以下几个问题:

  1. 产生异步问题。还是由于useEffect执行时机问题,在触发事件后,处理事件的回调函数如果在useEffect中则需要等到渲染结束后执行。这时如果有多个事件产生,执行的顺序可能和触发的顺序不一致。
  2. 重复的调用。如果组件重新挂载,useEffect可能会重复执行,在React的开发模式下,useEffect会默认执行两次,这样可以帮助开发者发现此类问题。

❌ 不推荐在useEffect中处理input改变事件。

const Comp = ({onChange}) => {
    useEffect(() => {
       onChange(inputText);
    }, [inputText])
    // ...
    <TextInput onChangeText={(text) => setInputText(text)} />
}

✅ 推荐在使用单独的处理函数处理input改变事件。

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中处理。

原文链接:https://juejin.cn/post/7332652230711328831 作者:justMonika

(0)
上一篇 2024年2月8日 上午10:31
下一篇 2024年2月8日 上午10:41

相关推荐

发表回复

登录后才能评论