别再让 React “白干活”了!useMemo 和 useCallback 的正确打开方式

3 阅读3分钟

你的组件是不是总在“无效加班”?明明数据没变,却一遍遍重新计算、重新渲染?别急,今天带你用 useMemouseCallback 给 React 装上“智能省电模式”!


🤔 问题来了:React 是不是太“勤快”了?

想象一下:你家有个特别勤快的机器人管家。
你只是想换个灯泡,它却把整个房子重新打扫了一遍——连你藏在沙发底下的薯片渣都不放过。

这不就是没优化的 React 吗?

const filterList = list.filter(item => item.includes(keyword));

看起来人畜无害的一行代码,但只要父组件里随便一个 count 状态变了,这个 filter 就会重新跑一遍!哪怕 keyword 根本没动!

更惨的是,如果你有个“超慢计算函数”(比如循环一亿次),那每次点击按钮,页面就卡成 PPT……用户可能以为你电脑中毒了 😅


💡 解药一:useMemo —— “记住结果,别瞎算”

useMemo 就像给 React 装了个“记忆芯片”:

“嘿,上次算过这个,依赖没变,直接给我缓存的结果就行,别又去算一亿次了!”

✅ 什么时候用?

  • 计算开销大(比如复杂过滤、排序、大数据处理)
  • 结果只依赖某些特定状态

🧪 示例:过滤水果列表

const filterList = useMemo(() => {
  return list.filter(item => item.includes(keyword));
}, [keyword]); // 只有 keyword 变了才重新算!

现在,你疯狂点 count +1 按钮?
filterList 纹丝不动!
→ 控制台不再疯狂输出 “filter 执行”!
→ 用户体验丝滑如德芙!

🎯 小贴士:别滥用!简单计算(比如 a + b)用 useMemo 反而增加开销。它适合“昂贵”的操作。


💡 解药二:useCallback —— “函数也要缓存!”

你以为只有数据会变?函数也会“变”

const handleClick = () => { console.log('click'); };

每次组件重新渲染,handleClick 都是一个全新的函数!即使代码一模一样。

这会导致什么问题?

👉 如果你把 handleClick 传给 memo 包裹的子组件,子组件会以为“props 变了”,于是不必要地重新渲染

✅ 怎么破?用 useCallback 缓存函数!

const handleClick = useCallback(() => {
  console.log('click');
}, [count]); // 依赖 count,只有 count 变了才生成新函数

配合 React.memo,子组件终于能安心“躺平”了:

const Child = memo(({ count, handleClick }) => {
  console.log('child 重新渲染'); // 只有 count 或 handleClick 真正变化时才触发
  return <div onClick={handleClick}>子组件 {count}</div>;
});

🎯 关键理解useCallback(fn, deps)useMemo(() => fn, deps)
它缓存的是函数本身,而不是函数的返回值。


🧠 深度思考:为什么 React 这么“傻”?

其实 React 一点都不傻!它的设计哲学是:

“简单优先,性能可选”

默认每次状态更新都重新执行整个函数组件,保证逻辑清晰、无副作用。
useMemo / useCallback手动优化工具——就像给汽车加涡轮增压,但你得知道什么时候踩油门、什么时候省油。

❌ 错误观念:“所有函数/计算都要包一层 useMemo/useCallback”
✅ 正确认知:“只在性能瓶颈处使用,先测量,再优化!”


🛠 实战建议:如何判断是否需要优化?

  1. 先写干净代码,别一上来就套 hooks。
  2. 用 React DevTools 查看组件重渲染情况。
  3. 发现卡顿 or 多余渲染 → 定位“昂贵操作”或“频繁传函数”。
  4. 针对性使用 useMemo / useCallback
  5. 验证效果:控制台日志 or 性能面板看是否减少计算/渲染。

🎉 总结:给 React 装上“节能开关”

场景工具作用
昂贵的计算(过滤、排序、大数据)useMemo缓存计算结果
传递给子组件的回调函数useCallback缓存函数引用,避免子组件无效重渲染

记住:优化是为了用户体验,不是为了炫技
别让你的 React 像那个勤快过头的机器人——该歇就歇,该算才算!


🔚 最后彩蛋:关于 "" 的 includes 陷阱

你提到:

includes 方法 ""也为true

没错!"apple".includes("") 返回 true,因为空字符串是任何字符串的子串。

所以当 keyword = "" 时,filterList 会返回全部项——这通常是预期行为(搜索框清空,显示全部)。但如果你不希望这样,记得加个判断:

useMemo(() => {
  if (!keyword.trim()) return list; // 或者 return []
  return list.filter(item => item.includes(keyword));
}, [keyword]);