react:Hook:useEffect

11 阅读3分钟

如果你用过 Vue 3 的 Composition API(setup 语法),你会觉得这非常眼熟。如果你主要用 Vue 2(Options API),那你需要转换一下思维:React 没有明确的 mountedupdateddestroyed 钩子,所有的生命周期都在 useEffect 里解决。


1. 什么是 useEffect?(副作用)

在 React 函数组件里,主流程(函数体)必须是纯净的:输入 Props,返回 JSX。

任何“除此之外”的事情,都叫副作用 (Side Effects) ,比如:

  • 修改 document.title
  • 发送 Ajax 请求 (API)
  • 设置定时器 (setInterval)
  • 手动操作 DOM

useEffect 就是 React 给你开的一个“后门”,让你在组件渲染完之后去做这些事。


2. Vue vs React:生命周期的映射

这是理解 useEffect 的一把钥匙。它的行为完全取决于第二个参数(依赖数组)

Vue 生命周期 (Options API)React useEffect 写法含义
mounted (只执行一次)useEffect(() => { ... }, [])数组是的,代表“我不依赖任何变量,所以我只在出生时运行一次”。
watch / updated (数据变了就执行)useEffect(() => { ... }, [count])数组里有 count,代表“只要 count 变了,我就运行”。
updated (任意更新都执行)useEffect(() => { ... })没写数组。每次组件渲染它都跑。(⚠️ 慎用,容易死循环)
beforeUnmount (销毁前)return () => { ... }useEffect 的函数里返回一个函数,这个返回的函数会在销毁时执行。

3. 实战演示:标题随计数器变化

需求: 每次点击按钮更新 count 时,浏览器的标签页标题 (document.title) 也要跟着变成 "当前次数: X"。

代码对比:

Vue 写法 (Watch)

JavaScript

// Vue 3
watch(count, (newVal) => {
  document.title = `当前次数: ${newVal}`;
});

React 写法 (useEffect)

JavaScript

import { useState, useEffect } from 'react'; // 1. 引入

function App() {
  const [count, setCount] = useState(0);

  // 2. 使用 useEffect
  // 翻译:当 [count] 发生变化之后,执行这个箭头函数
  useEffect(() => {
    document.title = `当前次数: ${count}`;
    console.log("副作用执行了!");
  }, [count]); // <--- 关键在这里!依赖数组

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
  );
}

实验:

如果你把 [count] 改成空数组 [],你会发现:只有刷新页面第一次标题变了,后面怎么点按钮,标题都不动。因为 React 认为“这个副作用不需要依赖任何状态更新”。


4. 难点:清理副作用 (Cleanup)

这是 Vue 开发者最容易懵的地方。

在 Vue 里,你在 mounted 里开启定时器,必须记得在 beforeUnmount 里清除。

在 React 里,这写在同一个地方

场景: 做一个自动计时器。

JavaScript

import { useState, useEffect } from 'react';

function Timer() {
  const [seconds, setSeconds] = useState(0);

  useEffect(() => {
    // 1. 开启定时器 (相当于 mounted)
    const intervalId = setInterval(() => {
      console.log("定时器在跑...");
      setSeconds(s => s + 1);
    }, 1000);

    // 2. 返回清理函数 (相当于 beforeUnmount)
    // React 会在组件销毁时,或者下一次 effect 执行前,调用这个函数
    return () => {
      console.log("清除定时器!");
      clearInterval(intervalId);
    };

  }, []); // [] 代表只在挂载时启动一次

  return <h1>已经过去了 {seconds} 秒</h1>;
}

逻辑闭环: React 认为“如何创建”和“如何销毁”是强相关的逻辑,所以强制你写在一起,防止你忘记清除。


💡 核心思维转换总结

  • Vue: 是基于时机的 (这时候该干嘛 -> mounted, destroyed)。
  • React: 是基于依赖的 (数据变了该干嘛 -> 依赖数组)。

你只要问自己两个问题:

  1. 我想做什么? (写在函数体里)

  2. 我想什么时候做?

    • 只有出生时? -> []
    • userId 变的时候? -> [userId]
    • 只要有风吹草动? -> 不传数组 (危险)