你的组件是不是总在“无效加班”?明明数据没变,却一遍遍重新计算、重新渲染?别急,今天带你用
useMemo和useCallback给 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”
✅ 正确认知:“只在性能瓶颈处使用,先测量,再优化!”
🛠 实战建议:如何判断是否需要优化?
- 先写干净代码,别一上来就套 hooks。
- 用 React DevTools 查看组件重渲染情况。
- 发现卡顿 or 多余渲染 → 定位“昂贵操作”或“频繁传函数”。
- 针对性使用 useMemo / useCallback。
- 验证效果:控制台日志 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]);