避免useEffect 无限循环

613 阅读2分钟

前言

useEffect(callback, dependencies) 是用于在函数式组件中处理副作用的钩子, callback 参数是一个函数,副作用的逻辑放到这个函数内部, dependencies 是副作用的依赖列表: 依赖项为 props 或 state 状态值。

useEffect 简单示例

useEffect(
  () => {
    console.log('I run when `a` changes')
  },
  [a]
)

useEffect 将在以下两种情况下运行:

  • 组件首次挂载时
  • 当变量 a 改变时。

下面的例子将导致 useEffect 无限循环

const [count, setCount] = useState(0)

// 这里当 count 变化时执行 useEffect 
// 又触发 count 改变函数,
// 然后又触发了useEffect 
// 如此无限循环
useEffect(
  () => {
    setCount(count + 1)
  },
  [count]
)

这是一个简单的示例。实际场景中我们会有这样的代码:

const [userData, setUserData] = useState({
  name: 'Jack',
  friends: ['alice', 'bob'],
})

// 也会无限运行,原因与上述相同
useEffect(
  () => {
    const newUser = {
      ...userData,
      friends: [...userData.friends, 'charlie'],
    }

    setUserData(newUser)
  },
  [userData]
)

这里我们 设置newUser 有两种方法:

setUserData(newUser)
setUserData(function(oldUser) {
  const newUser = {}
  return newUser
})

第一个获取新值并设置它。

第二个接受一个用旧值调用的函数,并返回新值。

让我们以前面的 useEffect 代码示例为例,将其更新为第二种形式:

const [userData, setUserData] = useState({
  name: 'Jack',
  friends: ['alice', 'bob'],
})

// 不会无限循环
useEffect(() => {
  setUserData(oldUser => {
    const newUser = {
      ...oldUser,
      friends: [...oldUser.friends, 'charlie'],
    }
    return newUser
  })
}, [])

这里我们不再需要依赖 userData ,因为我们从 setUserData 的回调函数中读取它!

这意味着我们的 useEffect 调用可以自由修改和设置新的用户数据而不用担心无限循环。

因此我们可以不用在 useEffect dependencies 数组中依赖他,这意味着当它改变时 useEffect 不会重新运行!

useEffect

副作用抽象本意是好的,也能看得出来官方想弱化生命周期的概念,但这太理想了,useEffect 包住所有副作用的想法也很 naive,佐证是后面的 useLayoutEffect,自己打自己脸,还不如一开始就分出多个 effect api。

React 会等待浏览器完成画面渲染之后才会延迟调用 useEffect

例子:改变css,改变块的位置,会出现拖影,一闪而过

可以把 useEffect Hook 看做 componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合。

useEffect 调度的 effect 不会阻塞浏览器更新屏幕,也就是他是异步的

useLayoutEffect 是同步的,api相同

useEffect 不能是异步函数(回调函数中使用 async...await... 的时候,会报错)

全文完

谢谢!

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 15 天

点击查看活动详情