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 次
