⚠️ useEffect和useCallback再乱写,你的代码就要炸了!

19 阅读3分钟

我们经常会看到这样的场景:一个组件改了 props,紧接着用 useEffect 重置 useState;或者每次函数都用 useCallback 包起来,生怕性能不够“丝滑”。其实,这些“反射式优化”不仅没有带来好处,反而拖慢了你的 UI,制造了更多不可预测的问题。

React 官方文档已经为这些常见问题给出了非常清晰的替代方案——你可能不需要 useEffect!👇

1. Effect重置状态

用Effect重置状态?你是在玩俄罗斯套娃渲染!

1.1 开局对比 state

❌错误示范:

function List({ items }) {
  const [selection, setSelection] = useState(null);
  useEffect(() => {
    setSelection(null); // 每次items变就重置?渲染两次!
  }, [items]);
}

后果:组件先用旧数据渲染 → 跑Effect → 再重新渲染 → 子组件连环爆炸

✅正确姿势:直接在渲染时暴力重置!

function List({ items }) {
  const [prevItems, setPrevItems] = useState(items);
  if (items !== prevItems) {
    setPrevItems(items);
    setSelection(null); // 一步到位,拒绝套娃!
  }
}

原理:React渲染阶段直接对比props,不!要!拖!到!Effect!

1.2 用key属性竟能秒杀嵌套状态

用key属性竟能秒杀嵌套状态?90%的人不知道!

❌错误示范:

// 用户ID变了?用Effect清空评论?太Low!
useEffect(() => setComment(""), [userId]);

后果:先渲染旧数据 → 再跑Effect → 再渲染新数据 → 性能直接扑街!

✅正确姿势:给组件一个key,让它原地去世重生!

export default function Profile({ userId }) {
  return <ProfilePage key={userId} userId={userId} />; // key一变,组件直接重置!
  
function ProfilePage({ userId }) {
  const [comment, setComment] = useState(""); // 自动清空,爽!
}
}

原理:key是组件的身份证号,一变就销毁旧组件,创建新实例,状态自动归零

2. useEffect清理函数

🚨不写清理函数?无脑 useEffect 拉取数据也可能出锅!

❌错误示范:

useEffect(() => {
  fetchResults(query).then(setResults); // 连续请求?后发的可能先到!
}, [query]);

后果:用户疯狂输入 → 请求乱序返回 → 页面显示错乱数据!

✅正确姿势:用ignore让陈年老请求自闭!

useEffect(() => {
  let ignore = false;
  fetchResults(query).then(json => {
    if (!ignore) setResults(json); // 只认最后一个请求!
  });
  return () => { ignore = true; }; // 清理函数一键截胡!
}, [query]);

原理:Effect卸载时标记ignore=true,过时响应直接原地丢弃!

3. useCallback 并非万金油

很多同学以为“函数传 props 会导致子组件重新渲染”,于是每个函数都包 useCallback,但其实:

  • 如果组件没有对该函数依赖进行 memo 或 React.memo 优化,useCallback 根本不会带来任何性能提升;
  • 而多余的 useCallback 会增加思维负担,甚至带来 bugs(依赖数组写错了都不知道)。

❌错误示范:

const handleSubmit = (orderDetails) => { /* ... */ };
// 每次渲染都创建新函数,memo子组件白给了!
<ShippingForm onSubmit={handleSubmit} />

后果:子组件疯狂重渲染 → 性能优化了个寂寞!

✅正确姿势:useCallback锁死函数,依赖不变就复用!

const handleSubmit = useCallback((orderDetails) => {
  post(`/products/${productId}/buy`, [referrer, orderDetails]);
}, [productId, referrer]); // 依赖不变,函数永远缓存!

原理:useCallback让函数身份不变,memo子组件直接跳过渲染!

React 团队不是让你少用 useEffect,是希望你更聪明地用 React 思维去解决问题。我们应该把副作用留给真正副作用的场景(如订阅、事件、请求) ,而不是用它来弥补对组件模型的理解盲区。

📎 参考资料
React 官方文档章节:You Might Not Need an Effect

📢 如果你觉得这篇文章对你有帮助,欢迎点赞 + 收藏 + 分享给更多 React 开发者 👇👇👇