React面试必问-useEffect小结,由浅到深

useEffect 是 React 提供的用于处理副作用的 Hook(副作用:数据请求、DOM 操作、订阅 / 取消订阅、定时器 / 延时器等)。useEffect 接收两个参数:

  • 第一个参数:副作用函数(必选),可以返回一个清理函数(可选);
  • 第二个参数:依赖数组(可选),存放副作用函数中用到的所有响应式变量(state、props、组件内定义的函数 / 变量)。

1.初级 useEffect怎么用?

  • 组件挂载后执行(依赖数组为空 []); componentDidMount
  • 依赖项变化时执行(依赖数组填指定变量);componentDidUpdate
  • 组件卸载时清理(返回清理函数);componentWillUnmount
  • 每次渲染后执行(如果不写第二个参数)。

2. 进阶-使用 useEffect 时遇到的坑点及解决方法

坑点1:依赖数组漏写 / 错写

  • 坑:副作用函数中用到了某个变量,但没写进依赖数组 → React 无法监控该变量变化,副作用不会重新执行;
const [count, setCount] = useState(0);

// 坑:用到了count,但依赖数组为空 → 点击按钮count变化,副作用不会重新执行

useEffect(() => {

 console.log(count);

}, []);
  • 解决:严格遵循「依赖数组包含副作用内所有用到的响应式变量」,可开启 ESLint 规则(react-hooks/exhaustive-deps)自动检查。

坑点2: 依赖数组放引用类型(对象 / 数组 / 函数)

  • 坑:React对对象实例进行监控,引用类型每次渲染会生成新引用 → 即使内容没变,也会触发副作用重复执行;
const [data, setData] = useState({ name: 'test' ,id:1});

// 坑:data是对象,每次渲染都是新引用 → 副作用每次都执行

useEffect(() => {

 console.log(data);

}, [data]);
    1. 解决对对象的某个属性进行监控,只有改属性发生变化,才会执行副作用
useEffect(() => {

 console.log(data);

}, [data.name]);
    1. 函数依赖:用 useCallback 缓存函数
      useCallback 会缓存函数的引用,只有当依赖项变化时,才会生成新函数,避免因引用变化触发副作用。
      解决方案代码:
import { useState, useEffect, useCallback } from 'react';
function Demo() {
  const [data, setData] = useState({ name: 'test' });
  // 用 useCallback 缓存函数:依赖 data.name(仅该属性变化时,函数引用才变)
  const handlePrintData = useCallback(() => {
    console.log('当前 data:', data.name); // 函数内用到 data.name,需加入依赖
  }, [data.name]); // 关键:依赖函数内用到的响应式数据(data.name)
  // 依赖缓存后的函数 → 仅 handlePrintData 引用变化(即 data.name 变化)时执行
  useEffect(() => {
    console.log('副作用执行(函数依赖-已缓存)');
    handlePrintData();
  }, [handlePrintData]);
  ...
}
 ...
}
    1. 对象依赖:用 useMemo 缓存,或只依赖具体属性(如 data.name)用 useMemo 缓存对象(推荐,适合需完整对象)useMemo 缓存对象引用,只有依赖项变化时才生成新对象。
import { useState, useEffect, useMemo } from 'react';
function Demo() {
  const [data, setData] = useState({ name: 'test' });

  // 用useMemo缓存对象,依赖age(只有age变化时,对象引用才变)
  const user = useMemo(() => {
    return { name: data.name, age: 18 }; // 对象内用到的变量加入依赖
  }, [data.name]);
  // 依赖缓存后的对象 → 只有user引用变化(即age变化)时才执行
  useEffect(() => {
    console.log('副作用执行(对象依赖-已缓存)', user);
  }, [user]);

 ...
}

坑点 3:闭包陷阱(拿不到最新的 state/props)

  • 现象:副作用函数中拿到的变量永远是初始值,即使变量已更新;
  • 原因:useEffect 的副作用函数捕获了组件渲染时的变量,若依赖数组未包含该变量,副作用不会重新执行,始终使用旧值;
const  [count, setCount] = useState(0);

useEffect(() => {

      setInterval(() => {
      // 永远输出0,因为依赖数组为空,捕获的是初始count
      console.log(count); 
 }, 1000);

}, []);
  • 解决:把变量加入依赖数组(推荐):
useEffect(() => {

        const timer = setInterval(() => console.log(count), 1000);
        return () => clearInterval(timer);

}, [count]); // 依赖count,每次count变化重新创建定时器
  • 若不想频繁创建定时器,用 useRef 保存最新值:
const countRef = useRef(count);

useEffect(() => {

    countRef.current = count; // 每次count更新,同步到ref

}, [count]);

useEffect(() => {
       setInterval(() => {
            console.log(countRef.current); // 拿到最新值
 }, 1000);

}, []);

坑点 2:清理函数执行时机理解错误

  • 现象:认为清理函数只在组件卸载时执行,忽略「依赖变化时也会执行」;
  • 原因:清理函数的执行时机是「下一次副作用执行前 + 组件卸载时」;明确清理函数的作用:不仅是卸载清理,也是「副作用更新前的收尾」;清理逻辑要和副作用逻辑对应(如创建定时器→清除定时器,发起请求→标记取消)。
const [id, setId] = useState(1);
useEffect(() => {
      console.log(`请求id: ${id}`);
      const timer = setTimeout(() => {}, 1000);
      return () => {
       console.log(`清理id: ${id}`); // id变化时会执行,不是只有卸载时
      clearTimeout(timer);
 };
}, [id]);

坑点 3:在 useEffect 中直接修改 state 导致无限循环

  • 现象:副作用函数中修改 state,且依赖数组包含该 state → 无限触发更新;
  • 示例:
const [count, setCount] = useState(0);

useEffect(() => {
       setCount(count + 1); // 修改count,依赖数组包含count → 无限循环
}, [count]);
  • 解决:若修改 state 不需要依赖旧值,用函数式更新:
useEffect(() => {
// 不依赖外部count,依赖数组为空
setCount(prev => prev + 1); 
}, []);
  • 若必须依赖,加条件判断限制执行:
useEffect(() => {

if (count < 10) {
 setCount(count + 1);
}
}, [count]);

坑点 4:忽略 useEffect 的异步执行特性

  • 现象:在 useEffect 中获取 DOM 元素,却想在组件渲染前拿到;
  • 原因:useEffect 是异步执行的,在 DOM 绘制后才执行,而 useLayoutEffect 是同步执行(DOM 绘制前);
  • 解决:
    • 普通 DOM 操作:用 useEffect 即可(DOM 已挂载);
    • 需同步操作 DOM(如测量尺寸、避免页面闪烁):用 useLayoutEffect

总结

  • 初级核心:掌握 useEffect 的基本用法、执行时机、依赖数组规则,避免漏写 / 错写依赖,正确处理耗时操作;

  • 进阶核心:理解 useEffect 的底层机制(Hook 链表、浅比较、执行阶段),解决闭包、重复执行、竞态等实战问题;

  • 关键原则:「依赖数组必须包含副作用内所有用到的响应式变量」+「清理函数与副作用逻辑对应」

3. 高手- useEffect的设计原理?(待补充)

为什么useEffect可以对属性进行监控 (待补充)
useEffect会被多次调用么?如何解决useEffect重复调用(待补充)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容