你可能并不需要useEffect

前言

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

处理渲染的数据

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

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

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

数据变形

比如需要通过拼接firstNamelastName来渲染完整的姓名,不推荐的方式是在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中会有以下几个问题:

  1. 产生异步问题。还是由于useEffect执行时机问题,在触发事件后,处理事件的回调函数如果在useEffect中则需要等到渲染结束后执行。这时如果有多个事件产生,执行的顺序可能和触发的顺序不一致。
  2. 重复的调用。如果组件重新挂载,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中处理。

相关推荐
kite01215 小时前
浏览器工作原理06 [#]渲染流程(下):HTML、CSS和JavaScript是如何变成页面的
javascript·css·html
крон5 小时前
【Auto.js例程】华为备忘录导出到其他手机
开发语言·javascript·智能手机
coding随想8 小时前
JavaScript ES6 解构:优雅提取数据的艺术
前端·javascript·es6
年老体衰按不动键盘8 小时前
快速部署和启动Vue3项目
java·javascript·vue
灵感__idea8 小时前
JavaScript高级程序设计(第5版):无处不在的集合
前端·javascript·程序员
星辰引路-Lefan8 小时前
深入理解React Hooks的原理与实践
前端·javascript·react.js
江城开朗的豌豆9 小时前
JavaScript篇:函数间的悄悄话:callee和caller的那些事儿
javascript·面试
江城开朗的豌豆9 小时前
JavaScript篇:回调地狱退散!6年老前端教你写出优雅异步代码
前端·javascript·面试
飞鸟malred9 小时前
vite+tailwind封装组件库
前端·react.js·npm
TE-茶叶蛋9 小时前
Vue Fragment vs React Fragment
javascript·vue.js·react.js