useEffect Hook

前言

副作用(side effect)是什么?

  • 在计算机科学中,如果一个函数或其他操作修改了其局部环境之外的状态变量值,那么它就被称为有副作用。

对于 React 组件来说:
主作用:就是根据数据(state/props)渲染 UI
副作用:比如,数据(Ajax)请求、手动修改 DOM、localStorage(token) 操作等等

当你想要在函数组件中,处理副作用(side effect)时,就要使用 useEffect Hook 了

  • 函数式组件除了渲染,其他操作都要通过useEffect
  • class类式组件除了render,其他操作都要在各种生命周期函数钩子

一、useEffect Hook 的基本使用

  • 使用场景:当你想要在函数组件中,处理副作用(side effect)时,就要使用 useEffect Hook
  • 作用:处理函数组件中的副作用(类似Vue里面的watch)

注意:在实际开发中,副作用是不可避免的。因此,react 专门提供了 useEffect Hook 来处理函数组件中的副作用

  • 使用步骤:
    1. 导入 useEffect 函数
    2. 调用 useEffect 函数,并传入参数(回调函数)
    3. 使用回调函数处理副作用

解释:

  • 参数:回调函数(称为 effect),就是在该函数中写副作用代码
  • 执行时机:该 effect 会在组件渲染后以及组件更新后执行
  • 相当于生命周期钩子函数componentDidMount + componentDidUpdate

二、useEffect 的参数

参数一:回调函数
参数二:依赖项(可选)
  • 依赖项(用于作状态的范围限制,只有设置的状态变化,才会触发)

1. 不设置依赖项目:默认只要函数组件内有任意状态发生变化,就会触发useEffect

2. 依赖项为有元素的数组:可以为多个状态元素,只要其中一个状态元素发生变化,就会触发useEffect

3. 依赖项为空数组:相当于componentDidMount,只有组件加载完毕会执行一次

注意

不要对useEffect的依赖项撒谎:
在useEffect回调函数中使用到的状态,必须在依赖项的数组中写明,useEffect才会生效(调用谁,依赖谁

三、class类式组件 对比 useEffect

  • 监听状态:实现count变化,标题也跟着变化。

class类式组件:

函数式组件useState Hook:

四、useEffect 清理副作用

有时候,我们只想在 React 更新 DOM 之后运行一些额外的代码。

  • 无需清楚的effect:比如发送网络请求,手动变更 DOM,记录日志,这些都是常见的无需清除的操作。
  • 需要清楚的effect:例如订阅外部数据源, 开启定时器,注册事件。这种情况下,清除工作是非常重要的,可以防止引起内存泄露!

内存泄漏:

内存泄漏(Memory Leak)是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

比如,组件开了计时器,组件已经销毁了,但是计时器没有销毁,占用内存,造成内存泄漏

在组件卸载时,要用到 effect 的返回值return

解释:

  1. effect 的返回值return也是可选的,可省略。也可以返回一个清理函数,用来执行事件解绑等清理操作
  2. 清理函数的执行时机:
    • 组件卸载时 (此时,相当于 class 组件的 componentWillUnmount 钩子函数的作用)
    • effect 重新执行前
  3. 推荐:一个 useEffect 只处理一个功能,有多个功能时,使用多次 useEffect
  4. 优势:根据业务逻辑来拆分,相同功能的业务逻辑放在一起,而不是根据生命周期方法名称来拆分代码
  5. 编写代码时,关注点集中;而不是上下翻滚来查看代码

五、将事件处理程序放在 useEffect 内部

  • 在给 window 绑定事件时,将 事件处理程序放在 useEffect 内部。
// 1 将 resize 事件处理程序放在 effect 回调中,当前这个代码是没有问题的
useEffect(() => {
  const handleResize = () => {
    console.log('window 窗口大小改变了')
  }
  window.addEventListener('resize', handleResize)

  return () => {
    window.removeEventListener('resize', handleResize)
  }
}, [])

// 2 将 resize 事件处理程序拿到 useEffect 的外部,当前这个代码是没有问题的
const handleResize = () => {
  console.log('window 窗口大小改变了')
}

useEffect(() => {
  window.addEventListener('resize', handleResize)

  return () => {
    window.removeEventListener('resize', handleResize)
  }
}, [])

// 3 有依赖项的情况:
useEffect(() => {
  // resize 事件的处理程序
  const handleResize = () => {
    console.log('window 窗口大小改变了', count)
  }

  window.addEventListener('resize', handleResize)

  return () => {
    window.removeEventListener('resize', handleResize)
  }
}, [count])


// 注意:此处的代码,会给一些警告!!! 不要按照这种方式写代码!!!
// 4 如果将 handleResize 放到了 useEffect 外部,React 会给以警告:
//   要么将 handleResize 放到 useEffect 中
//   要么使用 useCallback 这个 hook 来包裹 handleResize
// resize 事件的处理程序
const handleResize = () => {
  console.log('window 窗口大小改变了', count)
}
useEffect(() => {
  console.log('useeffect 执行了')
  window.addEventListener('resize', handleResize)

  return () => {
    window.removeEventListener('resize', handleResize)
  }
}, [handleResize])

// 总结以上几种情况,推荐:在给 window 绑定事件时,将 事件处理程序放在 useEffect 内部。

六、useEffect 发送请求

在组件中,使用 useEffect Hook 发送请求获取数据(side effect):

useEffect(() => {
  //在内部定义一个发请求的方法
  const loadData = async () => {}
  loadData()
}, [])

解释:

  • 注意:effect 只能是一个同步函数,不能使用 async
  • 因为 effect 的返回值return要求是一个清理函数,React 会在组件卸载或者 effect 的依赖项变化时重新执行
  • 但如果 effect 是 async 的,此时返回值是 Promise 对象。这样的话,就无法保证清理函数被立即调用
  • 如果延迟调用清理函数,也就没有机会忽略过时的请求结果或取消请求
  • 为了使用 async/await 语法,可以在 effect 内部创建 async 函数,并调用
// 错误演示:

// 不要给 effect 添加 async
useEffect(async () => {}, [])
// https://github.com/facebook/react/issues/14326#issuecomment-441680293

useEffect(() => {
  // 是否取消本次请求
  let didCancel = false
  //在内部定义一个发请求的方法
  async function fetchMyAPI() {
    let url = 'http://something/' + productId
    let config = {}
    const response = await myFetch(url)
    // 如果开启其他请求,就忽略本次(过时)的请求结果
    if (!didCancel) {
      console.log(response)
    }
  }

  fetchMyAPI()
  return () => { didCancel = true } // 取消本次请求
}, [productId])

七、useEffect循环报错问题

  • 依赖了list,只要list发生变化,就会重新请求数据。
  • 数组之间比较的下标地址,两个数据相同的数组不相等,会导致死循环,一直请求数据
useEffect(() => {
  const getList = async () => {
    const res = await axios.get('http://geek.itheima.net/v1_0/channels')
    setList(res.data.data.channels)
  }
  console.log(list)
  getList()
}, [list])
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容