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% 的业务项目。

相关推荐
程序员海军4 小时前
用了两年多 GPT,我最近开始换模型了,还折腾了个求职避坑 Agent
前端·人工智能·后端
码士集团小青4 小时前
SpringBoot 集成缓存性能之王 Caffeine
spring boot·后端·缓存
excel6 小时前
IndexNow:让搜索引擎更快发现网站更新的主动通知协议
前端·后端·架构
Json____6 小时前
用 FastAPI + Vue3 打造一套完整的在线考试系统:从组卷到阅卷的业务闭环实践
后端·node.js·fastapi·it学习·wwwoop.com
小灰灰搞电子7 小时前
Rust+Slint 实现FTP客户端调试助手源码分享
开发语言·后端·rust
专业程序开发源7 小时前
springboot欧缇蔓月子中心管理系统82697-计算机课程设计、毕业设计
java·spring boot·后端·python·spring·django·课程设计
卷无止境8 小时前
面向arm64桌面客户端开发全景:语言、框架与生态
后端·python
IT_陈寒8 小时前
JavaScript的闭包陷阱,这个坑我躺了三天才爬出来
前端·人工智能·后端
用户8356290780518 小时前
如何使用 Python 对 Excel 数据进行分组和分级显示
后端·python
vx_Biye_Design9 小时前
django房屋租赁推荐系统78455-计算机课程设计、毕业设计
vue.js·spring boot·后端·python·django·课程设计·express