React 严格模式与双调用:useEffect 重复执行、状态初始化陷阱与规避

test12026-10-210 次阅读

为什么要有严格模式

StrictMode 只在开发环境生效。它的核心手段之一,是把组件函数、某些 Hook 的初始化逻辑重复调用一次,用“多跑一遍”的方式把不纯的代码、藏起来的副作用暴露在开发期。生产构建不会双调用,所以这不是性能问题,而是开发期的体检。

useEffect 会被调用两次

最常见的现象:组件挂载时,effect 里发起了请求,开发期你会看到两条请求。这是因为 React 先挂载、再卸载、再挂载,effect 的清理函数会被调用一次。如果清理没写好,就会出现竞态、重复写入。下面的写法用 active 标记挡住过期的响应。

import { useEffect, useState } from 'react';

export function UserList() {
  const [users, setUsers] = useState<string[]>([]);

  useEffect(() => {
    let active = true;
    fetch('/api/users')
      .then((r) => r.json())
      .then((data) => {
        if (active) setUsers(data);
      });
    // 清理函数:第二次挂载前先执行,active 置 false 让旧的响应失效
    return () => {
      active = false;
    };
  }, []);

  return (
    <ul>
      {users.map((u) => (
        <li key={u}>{u}</li>
      ))}
    </ul>
  );
}

状态初始化的陷阱

useState 的惰性初始化器只应在首次渲染时返回一个值,不能在里面写副作用。下面的错误示范会在 StrictMode 下被调用两次,白白消耗一个自增 id。

// 错误示范:在初始化器里写副作用
let nextId = 1;
const [id] = useState(() => {
  return nextId++; // StrictMode 下调用两次,浪费一个 id
});

// 正确:把“产生副作用的序列号”放到 effect 里,或用 useRef 惰性获取
const idRef = useRef<number | null>(null);
if (idRef.current === null) {
  idRef.current = allocateIdOnce();
}

规避策略

  • 把副作用全部搬进 useEffect,并在清理函数里撤销(定时器、订阅、请求标记)。
  • 让组件函数本身保持纯净:同样的 props 返回同样的结构,不偷偷改外部变量。
  • 惰性初始化器只做纯计算(读一次 localStorage 可以,发请求不行)。
  • 需要一次性副作用时,用 ref + 守卫,而不是依赖“只跑一次”的错觉。

记住:生产环境不会双调用,所以这些双调用只发生在你本地调试时。它们不是 bug,而是提醒你把代码写得更健壮。

T

test1

文章作者

为什么要有严格模式 StrictMode 只在开发环境生效。它的核心手段之一,是把组件函数、某些 Hook 的初始化逻辑...

分类
技术
发布时间
2026-10-21
字数
约 1394 字
阅读
0 次