React 播客专栏 Vol.14|useEffect 背后都在忙些什么?

👋 欢迎回到《前端达人 · React 播客书单》第 14 期(正文内容为学习笔记摘要,音频内容是详细的解读,方便你理解),请点击下方收听

视频版

今天我们聚焦一个让人又爱又怕的 Hook —— useEffect,它是副作用的管理器,是函数组件的“隐藏执行者”。

我们将从基础语法,到执行机制,再到真实开发场景,完整拆解 useEffect 的使用逻辑与工作原理。

一、你为什么用不好 useEffect

很多人第一次用 useEffect,就遇到这些问题:

🌀 “为什么副作用执行了两次?” 😱 “组件卸载时怎么还在请求接口?” 💣 “useEffect 写在按钮里怎么报错了?”

这些其实都跟它的规则和生命周期时机有关。

我们来带着这些问题,逐个拆解。

二、什么是副作用?(Side Effects)

📌 通俗解释

副作用 = 不直接参与 UI 渲染、但“影响了组件生命周期”的逻辑行为

📦 常见副作用包括:

  • 接口请求(fetch, axios)

  • 添加 DOM 事件监听

  • 设置定时器、interval

  • 第三方库的注册 / 订阅

  • 页面标题修改、手动操作 DOM

👀 可以类比成做饭时顺手擦桌子 —— 主任务是做饭,擦桌子是副任务,但它依然需要安排好时机执行。

三、基本语法与用法

useEffect 的标准写法如下:

useEffect(() => {
  // 副作用代码
}, [依赖项]);

解释:

  • 第一参数:副作用函数(Effect Function)

  • 第二参数:依赖项数组(Dependencies Array)

📌 不同依赖写法 = 不同执行时机:

写法

执行时机说明

无依赖项

每次渲染后都会执行

空数组 []

仅在挂载时执行一次(相当于 componentDidMount

有依赖项 [search]

仅当指定依赖项发生变化时才重新执行副作用

代码示例:

// 每次渲染后都执行
useEffect(() => {
  console.log("组件更新了");
});

// 指定依赖项
useEffect(() => {
  console.log("search 变化了");
}, [search]);

// 只在挂载时执行一次
useEffect(() => {
  console.log("组件加载了");
}, []);

✅ 推荐每次写 useEffect 都明确依赖数组,避免无意识触发。

四、注意!useEffect 的两条硬性规则

🧱 Hooks 规则必须记牢

  1. ✅ 只能在函数组件最顶层调用❌ 不能放在条件语句、循环、嵌套函数中

  2. ✅ 只能在函数组件或自定义 Hook 中调用❌ 不可在普通函数或 class 组件中使用

错误示例:

export function BadComponent() {
  function handleClick() {
    // ❌ 错误:在事件处理函数中调用 Hook
    useEffect(() => {
      console.log("点击副作用");
    });
  }

  return <button onClick={handleClick}>Click</button>;
}

五、useEffect 的执行机制图解 🧠

生命周期时机

useEffect 行为

初次渲染完成

执行副作用函数

依赖项变化(非空)

清除上一次副作用 → 执行新的副作用函数

组件卸载

清除副作用

📌 精髓:

  • 有依赖项 → 比较依赖变化 → 触发副作用

  • 副作用函数返回值是“清理函数” → 下一次执行前/卸载时运行

六、清理函数:打扫战场的副将

清理函数是 useEffect 返回的函数,它会在副作用重跑前或组件卸载时被调用

常见用途:

  • 清除事件监听器

  • 取消订阅

  • 清除定时器

  • 终止请求

代码示例:

useEffect(() => {
  const handler = () => console.log("点击");
  document.addEventListener("click", handler);

  return () => {
    document.removeEventListener("click", handler); // 清理
  };
}, []);

🧼 类比生活:副作用是吃完饭,清理函数就是洗碗 ✨

七、实战:数据获取场景中的最佳实践

👨‍💻 获取后端数据,是 useEffect 最典型的用法之一。

🎯 场景:组件加载时请求数据并展示

export function PostsPage() {
  const [posts, setPosts] = useState<PostData[]>([]);

  useEffect(() => {
    getPosts().then((data) => {
      setPosts(data);
    });
  }, []);
}

📌 Tips:

  • 如果使用 async/await,记得 不能直接在 useEffect 外层用 async!

useEffect(() => {
  async function fetchData() {
    const res = await fetch('/api/posts');
    const data = await res.json();
    setPosts(data);
  }

  fetchData(); // 内部调用 async 函数
}, []);

八、进阶优化:处理加载状态 & 错误处理

✅ 推荐增加 isLoading 和 error 状态来更好管理异步流程:

export function PostsPage() {
  const [isLoading, setIsLoading] = useState(true);
  const [posts, setPosts] = useState([]);
  const [error, setError] = useState(null);

  useEffect(() => {
    let ignore = false;

    async function fetchData() {
      try {
        const res = await fetch('/api/posts');
        const data = await res.json();
        if (!ignore) setPosts(data);
      } catch (err) {
        if (!ignore) setError(err);
      } finally {
        if (!ignore) setIsLoading(false);
      }
    }

    fetchData();

    // 清理副作用
    return () => {
      ignore = true;
    };
  }, []);
}

📌 说明:

  • 使用 ignore flag 防止组件卸载后仍更新状态

  • 真实项目中也可使用 AbortController 中断请求

九、React 严格模式下副作用执行两次?

⚠️ 在开发模式中,React 18 严格模式(StrictMode)下会故意模拟卸载 + 重建

  • 你会看到副作用执行两次,这是“检测副作用是否安全”的设计

  • 生产环境中只会执行一次,不用担心

🔟 总结回顾 🧠

✅ 关键词

📌 要点说明

副作用(Side Effect)

组件渲染之外的逻辑:获取数据、DOM 操作、订阅等

useEffect()

处理副作用的 Hook,配合依赖项控制执行时机

清理函数

返回值函数:在下一次执行/卸载前清除副作用

异步请求最佳实践

使用 async/await + 错误处理 + loading 状态管理

Hook 使用规则

只能顶层调用,不能用于 class 组件、if/for 结构中

👋 喜欢这一期的朋友别忘了:

点赞、转发、收藏!有任何问题也欢迎在评论区留言,我会逐条解答!

我们下期见!🧑‍💻✨

#React       #React播客      #前端达人       #前端播客      #CSS  #TypeScript  #TailwindCss  #SVG   
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值