首页 / React 19 入门教程 / useEffect 心智模型

React 19 入门教程

useEffect 心智模型

本教程共 50 篇 · 第 27 篇 · 更新于 2026-07-29 · 约 8 分钟阅读

ReactuseEffect副作用依赖数组同步

27. useEffect 心智模型

本节目标:建立 useEffect 的正确心智模型,理解渲染与副作用的区别,掌握依赖数组的用法。

两种代码:渲染逻辑 vs 副作用

React 组件里有两种代码:

渲染逻辑(纯函数,位于组件顶层):

function ChatRoom({ roomId }) {
  const [message, setMessage] = useState('');
  // 根据 props 和 state 计算 JSX
  return <div>{message}</div>;
}

渲染逻辑必须是纯的——同样的输入永远返回同样的 JSX,不能修改外部世界。

副作用(与外部系统同步,位于 useEffect 内):

useEffect(() => {
  // 连接服务器、订阅事件、操作 DOM
  const connection = createConnection(roomId);
  connection.connect();
}, [roomId]);

副作用就是”跳出 React 做的事”——连接网络、操作 DOM、订阅事件。

为什么叫”副作用”

在函数式编程里,纯函数只做计算。任何修改外部世界的行为都叫”副作用”。Effect 就是 React 管理副作用的机制。

useEffect 的基本结构

useEffect(() => {
  // effect 主体:每次同步时执行
  return () => {
    // 清理函数:每次重新同步前执行
  };
}, [依赖数组]);

三个部分:

  1. 主体:同步到最新状态
  2. 清理函数:停止旧同步
  3. 依赖数组:控制何时重新同步

依赖数组决定什么时候跑

// 没有依赖数组:每次渲染后都跑
useEffect(() => {
  console.log('每次渲染都执行');
});

// 空依赖数组:只跑一次(挂载时)
useEffect(() => {
  console.log('只在首次渲染执行');
}, []);

// 有依赖:依赖变化时才跑
useEffect(() => {
  console.log(`房间切换到 ${roomId}`);
}, [roomId]);

依赖不能省略

effect 里用的所有响应式值(props、state、组件内变量)都要写进依赖数组。否则 effect 读到的是过期值。

一个聊天室的例子

function ChatRoom({ roomId }) {
  useEffect(() => {
    const connection = createConnection(roomId);
    connection.connect();
    console.log(`连接到 ${roomId}`);

    return () => {
      connection.disconnect();
      console.log(`断开 ${roomId}`);
    };
  }, [roomId]);

  return <h1>当前房间:{roomId}</h1>;
}

切换 roomId 时:

  1. React 用新 roomId 渲染
  2. 执行清理函数:断开旧房间
  3. 执行 effect 主体:连接新房间

effect 不是生命周期

新手容易把 useEffect 类比为 componentDidMountcomponentDidUpdate。这是错的。

组件生命周期:挂载 → 更新 → 卸载

effect 生命周期:开始同步 → 停止同步 → 开始同步 → …

effect 的关注点是”同步”,不是”生命周期阶段”。同一个 effect 可能执行多次。

// 错误心智模型
useEffect(() => {
  // 这不只是"挂载时"执行,依赖变化也会执行
}, []);

// 正确心智模型
useEffect(() => {
  // 开始同步到 roomId
  return () => {
    // 停止同步到旧的 roomId
  };
}, [roomId]);

effect 的执行时机

React 在浏览器绘制之后才执行 effect。这意味着:

  1. 组件函数执行(渲染)
  2. React 更新 DOM
  3. 浏览器绘制页面
  4. React 执行 effect

所以 effect 不会阻塞页面渲染。

为什么是绘制之后

如果 effect 在绘制之前执行,用户可能看到闪烁。React 确保用户先看到更新后的页面,再执行副作用。

实战:订阅和取消订阅

function OnlineStatus() {
  const [isOnline, setIsOnline] = useState(true);

  useEffect(() => {
    function handleOnline() { setIsOnline(true); }
    function handleOffline() { setIsOnline(false); }

    window.addEventListener('online', handleOnline);
    window.addEventListener('offline', handleOffline);

    return () => {
      window.removeEventListener('online', handleOnline);
      window.removeEventListener('offline', handleOffline);
    };
  }, []); // 空依赖:只订阅一次

  return isOnline ? '在线' : '离线';
}

实战:根据 props 获取数据

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    setLoading(true);
    fetch(`/api/users/${userId}`)
      .then(res => res.json())
      .then(data => {
        setUser(data);
        setLoading(false);
      });
  }, [userId]); // userId 变化时重新获取

  if (loading) return <p>加载中...</p>;
  return <p>{user.name}</p>;
}

常见心智模型错误

错误认知正确认知
useEffect 是生命周期useEffect 是同步机制
依赖数组控制”是否执行”依赖数组控制”何时重新同步”
清理函数只在卸载时跑清理函数在每次重新同步前跑
effect 在渲染前执行effect 在浏览器绘制后执行

本节要点

  • 渲染逻辑是纯的,副作用在 useEffect 里
  • 依赖数组控制 effect 何时重新同步
  • 清理函数在重新同步前执行
  • effect 是同步机制,不是生命周期