React Hooks 常见 5 大坑:写完代码疯狂 Bug,避坑总结

React Hooks 常见 5 大坑:写完代码疯狂 Bug,避坑总结

React Hooks 自 16.8 发布以来,已经彻底改变了我们写 React 的方式。函数组件 + Hooks 的组合,让逻辑复用和组件拆分变得前所未有的简单。

但"简单"不等于"安全"。很多同学刚上手 Hooks 时,写着写着就开始踩坑:状态不更新、无限循环、内存泄漏、依赖混乱......甚至一度怀疑人生。

这篇文章,我总结了 React Hooks 最常见的 5 大坑,每个坑都配真实场景 + 错误示例 + 正确写法 + 原理解析,帮你一次避坑到底。


坑一:useEffect 依赖写错,状态"永远不更新"

❌ 常见错误

scss 复制代码
const [count, setCount] = useState(0);

useEffect(() => {
  console.log('count changed:', count);
}, []);

你以为:count 变了,useEffect 会重新执行。

实际:只执行一次 ,后续 count 变化完全被忽略。

原因

useEffect 的依赖数组是 静态比较​ 的。

[] 表示"没有任何依赖",React 会认为这个 effect 永远不需要重新执行

✅ 正确写法

scss 复制代码
useEffect(() => {
  console.log('count changed:', count);
}, [count]);

💡 避坑建议

  • 永远不要凭感觉写依赖
  • 开启 eslint-plugin-react-hooks
  • 让 ESLint 告诉你缺了什么依赖

✅ 一句话总结:依赖写错,等于逻辑写死。


坑二:useState 异步更新 + 闭包陷阱

❌ 常见错误

scss 复制代码
const [count, setCount] = useState(0);

const handleClick = () => {
  setCount(count + 1);
  setCount(count + 1);
};

你以为:count 会变成 2

实际:永远是 1

原因

  • setCount异步的
  • handleClick 形成闭包,count 始终是旧值

✅ 正确写法(函数式更新)

ini 复制代码
const handleClick = () => {
  setCount(prev => prev + 1);
  setCount(prev => prev + 1);
};

💡 避坑建议

  • 连续更新状态 → 用函数式更新
  • 依赖"当前状态"计算下一个状态 → 必须用 prev

✅ 一句话总结:状态是快照,不是实时变量。


坑三:useEffect 里写异步函数,内存泄漏警告满天飞

❌ 常见错误

ini 复制代码
useEffect(() => {
  fetch('/api/user')
    .then(res => res.json())
    .then(data => setUser(data));
}, []);

组件卸载后,请求才返回,控制台直接报警:

vbnet 复制代码
Can't perform a React state update on an unmounted component.

原因

组件已经销毁,但异步回调还在尝试 setUser,React 直接骂人。

✅ 正确写法(清理副作用)

ini 复制代码
useEffect(() => {
  let ignore = false;

  fetch('/api/user')
    .then(res => res.json())
    .then(data => {
      if (!ignore) {
        setUser(data);
      }
    });

  return () => {
    ignore = true;
  };
}, []);

✅ 更现代写法(AbortController)

scss 复制代码
useEffect(() => {
  const controller = new AbortController();

  fetch('/api/user', { signal: controller.signal })
    .then(res => res.json())
    .then(setUser)
    .catch(err => {
      if (err.name !== 'AbortError') {
        console.error(err);
      }
    });

  return () => controller.abort();
}, []);

💡 避坑建议

  • 所有副作用都要考虑清理
  • 请求、订阅、定时器、事件监听,一个都不能少

✅ 一句话总结:不清理副作用,就是在埋雷。


坑四:useMemo / useCallback 乱用,反而更慢

❌ 常见错误

scss 复制代码
const double = useMemo(() => count * 2, [count]);

你以为:性能优化了。

实际:反而更慢

原因

  • useMemo 本身有 比较依赖 + 缓存开销
  • 简单计算 + 频繁更新 = 负优化

✅ 正确写法

ini 复制代码
const double = count * 2;

✅ 真正适合 useMemo / useCallback 的场景

  • 作为 props 传给 React.memo 包裹的子组件
  • 作为 依赖项 传给其他 Hooks
  • 计算成本极高的派生状态
scss 复制代码
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

💡 避坑建议

  • 不要为了"看起来高级"而用
  • 性能优化 = 先测量,再优化

✅ 一句话总结:过早优化,是 Hooks 的头号杀手。


坑五:自定义 Hook 里"偷偷"共享状态

❌ 常见错误

ini 复制代码
let globalState = 0;

function useSharedState() {
  const [state, setState] = useState(globalState);

  const increment = () => {
    globalState++;
    setState(globalState);
  };

  return [state, increment];
}

多个组件使用 useSharedState,结果状态不同步,甚至互相覆盖。

原因

  • Hooks 不是单例
  • 每个组件调用 Hook,都会创建 独立的状态实例
  • 模块级变量 ≠ React 状态

✅ 正确写法(状态提升 / Context / 状态库)

scss 复制代码
const CountContext = createContext();

function useSharedState() {
  const [count, setCount] = useContext(CountContext);
  return [count, setCount];
}

或直接使用 Zustand / Redux / Jotai。

💡 避坑建议

  • 自定义 Hook ≠ 全局状态
  • 需要共享状态 → 明确使用状态管理方案

✅ 一句话总结:Hook 是逻辑复用,不是状态共享。


总结:5 大坑一张表

本质问题 避坑口诀
useEffect 依赖写错 闭包 + 依赖比较 依赖不写全,逻辑必翻车
useState 闭包陷阱 状态是快照 连续更新用函数
useEffect 不清理 内存泄漏 副作用必须清
useMemo 乱用 过度优化 先慢再快,别瞎猜
自定义 Hook 共享状态 误解 Hook 模型 Hook 复用 ≠ 状态共享

写在最后

React Hooks 的坑,本质上只有一句话:

你以为你写的是"命令式代码",其实你写的是"声明式 + 闭包 + 调度系统"。

Hooks 不难,难的是 思维模式的切换

如果你能避开这 5 个坑,你的 React 代码质量,至少能超过 80% 的业务项目

相关推荐
小强19881 小时前
useRef 到底用来干什么?不止获取 DOM 元素
后端
颜进强1 小时前
04 - 一张图看懂 OpenSpec 变更的一生:从创建到归档
前端·后端·ai编程
Cobyte1 小时前
Claude Code 的 Task System 的实现原理
后端·aigc·ai编程
小强19881 小时前
useEffect 完整使用指南:依赖数组、闭包陷阱、清理函数实战
后端
智驭未来掌门人1 小时前
在windows下快速搭建go2rtc流媒体平台
后端
大白801 小时前
为什么不要滥用 useMemo 和 useCallback?过度优化反而更卡
后端
神奇小汤圆2 小时前
Codex 子 Agent 配置指南:让 Sol 当军师,Luna 当搬砖工
后端
ClouGence2 小时前
2026 年 4 款数据库管理工具推荐:免费、开源、付费怎么选?
数据库·后端·开源
大勇前进2 小时前
React 中 State 为什么不能直接修改?很多新手一直没懂
后端