主要用来执行副作用

使用

import { useEffect } from 'react';
 
function MyComponent() {
  useEffect(() => {
    // 每次渲染后都会执行此处的代码
  });
  
  return <div />;
}

执行时机

每当组件渲染时,React 会先更新页面,然后再运行 useEffect 中的代码。换句话说,useEffect 会“延迟”一段代码的运行,直到渲染结果反映在页面上。

  1. 触发渲染:组件状态或 props 变化
  2. 渲染阶段:计算新的虚拟 DOM
  3. 提交阶段:更新真实 DOM
  4. useLayoutEffect 执行:同步处理
  5. 浏览器绘制:用户看到更新后的界面
  6. useEffect 执行:异步处理副作用

依赖项

  • useEffect(() => {}):相当于 componentDidMount + componentDidUpdate(会在页面渲染之后执行,即挂载后和每次更新后都执行)
  • useEffect(() => {}, []):相当于 componentDidMount(组件首次挂载后执行,仅在组件挂载时执行一次)
  • useEffect(() => {}, [xxx]):如果想跳过对 useEffect 的调用,只需要传入第二个参数依赖项(相当于类组件中 componentDidUpdate

响应式值(state、props、在组件中直接声明的值都是响应式值,从它们计算出的值也是响应式的)必须包含在依赖项中,依赖性使用 Object.is 进行比较

最佳实践

保持依赖项稳定:

  • 对象/数组:使用 useMemo 或移到组件外部
  • 函数:使用 useCallback 或直接在 useEffect 内部定义

返回值

  • 回调函数中可以返回一个清除函数,相当于类组件中 componentWillUnmount 生命周期函数

React 会在每次 Effect 重新运行之前调用清理函数,并在组件卸载(被移除)时最后一次调用清理函数。

与事件的区别:

  • 事件由交互引起(手动触发,非响应式)
  • effect 由渲染本身引起(自动触发,响应式)

Warning

不要把需要在事件中进行触发的逻辑写在 useEffect 中

开发环境下 useEffect 运行了两次?

React 故意在开发环境下重新挂载你的组件,以此帮助你找到没有写清理函数的 useEffect

注意

  • 特定的交互产生的行为应该将该逻辑直接放到相应的事件处理程序中而不是 useEffect 中
  • 避免: 单个 Effect 同步两个独立逻辑处理
  • 避免: 将对象和函数作为 Effect 的依赖,这些值在组件重新渲染时会创建新的对象(尝试将它们移到组件外部、Effect 内部,或从中提取原始值)

useLayoutEffect

在浏览器绘制之前执行

正常 99% 的情况下都用 useEffect,仅在出现视觉问题时考虑 useLayoutEffect,但是会有性能问题