你可能并不需要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中处理。

相关推荐
海的诗篇_1 小时前
前端开发面试题总结-原生小程序部分
前端·javascript·面试·小程序·vue·html
黄瓜沾糖吃2 小时前
大佬们指点一下倒计时有什么问题吗?
前端·javascript
温轻舟2 小时前
3D词云图
前端·javascript·3d·交互·词云图·温轻舟
浩龙不eMo3 小时前
✅ Lodash 常用函数精选(按用途分类)
前端·javascript
爱分享的程序员3 小时前
前端面试专栏-算法篇:17. 排序算法
前端·javascript·node.js
Jackson_Mseven3 小时前
面试官:useEffect 为什么总背刺?我:闭包、ref 和依赖数组的三角恋
前端·react.js·面试
pe7er3 小时前
使用 Vue 官方脚手架创建项目时遇到 Node 18 报错问题的排查与解决
前端·javascript·vue.js
pe7er4 小时前
使用 types / typings 实现全局 TypeScript 类型定义,无需 import/export
前端·javascript·vue.js
islandzzzz4 小时前
(第二篇)HMTL+CSS+JS-新手小白循序渐进案例入门
前端·javascript·css·html
喝拿铁写前端4 小时前
前端实战优化:在中后台系统中用语义化映射替代 if-else,告别魔法数字的心智负担
前端·javascript·架构