React 中 useMemo 和 useCallback 什么时候该用什么时候不该用?
React 中 useMemo 和 useCallback 什么时候该用什么时候不该用?
一句话总结:不是所有函数和对象都要包一层。只有「传给子组件的引用」或「依赖数组的依赖项」需要稳定引用时才用,否则纯属添乱!
正文目录
- useMemo / useCallback 到底解决什么问题?
- 5 大高频误用场景 & 修复代码
- 万能兜底:三个问题判断该不该用
- 预防 checklist(不再踩坑)
- 一句话总结
一、useMemo / useCallback 到底解决什么问题?
useMemo缓存计算结果,useCallback缓存函数引用。
// useMemo:缓存计算结果 const expensiveValue = useMemo(() => compute(a, b), [a, b]); // useCallback:缓存函数引用 const handleClick = useCallback(() => doSomething(id), [id]);本质:它们解决的不是性能问题,而是引用稳定性问题。
React 组件每次渲染,函数体内的变量和函数都会重新创建。如果你把这些新创建的函数/对象传给子组件或放进依赖数组,子组件就会不必要地重渲染,effect 也会不必要地重复执行。
一句话:引用不稳定 → 子组件白渲染 / effect 白执行 → 才需要 useMemo/useCallback。
二、5 大高频误用场景 & 修复代码
① 给没有性能问题的计算包了 useMemo
// ❌ 简单计算包 useMemo,缓存本身比计算还贵 function UserList({ users }) { const count = useMemo(() => users.length, [users]); // ❌ 求个长度还缓存? return <div>共 {count} 人</div>; }修复:直接算
// ✅ 简单计算不需要缓存 function UserList({ users }) { const count = users.length; // ✅ 直接取 return <div>共 {count} 人</div>; }② 给不传给子组件的函数包了 useCallback
// ❌ 函数只在当前组件用,包了 useCallback 没意义 function Form() { const [value, setValue] = useState(''); const handleChange = useCallback((e) => { // ❌ 没传给子组件,纯浪费 setValue(e.target.value); }, []); return <input value={value} onChange={handleChange} />; }修复:原生元素不需要稳定引用
// ✅ 原生 input 不需要 useCallback function Form() { const [value, setValue] = useState(''); const handleChange = (e) => { // ✅ 直接定义 setValue(e.target.value); }; return <input value={value} onChange={handleChange} />; }③ 传给 memo 子组件的函数没包 useCallback,memo 白写了
// ❌ 子组件用了 memo,但父组件每次传新函数引用 → memo 失效 const Child = memo(({ onClick }) => { console.log('Child rendered'); // ❌ 每次父组件渲染都触发 return <button onClick={onClick}>Click</button>; }); function Parent() { const [count, setCount] = useState(0); const handleClick = () => { // ❌ 每次渲染新函数 console.log('clicked'); }; return ( <> <button onClick={() => setCount(c => c + 1)}>+1</button> <Child onClick={handleClick} /> </> ); }修复:用 useCallback 稳定引用
// ✅ useCallback 稳定引用,memo 生效 const Child = memo(({ onClick }) => { console.log('Child rendered'); // ✅ 只有 props 变才渲染 return <button onClick={onClick}>Click</button>; }); function Parent() { const [count, setCount] = useState(0); const handleClick = useCallback(() => { // ✅ 引用稳定 console.log('clicked'); }, []); return ( <> <button onClick={() => setCount(c => c + 1)}>+1</button> <Child onClick={handleClick} /> </> ); }④ useCallback 的依赖项写错,闭包陷阱
// ❌ 依赖数组写了空,但函数里用了 state → 闭包旧值 function Timer() { const [count, setCount] = useState(0); const handleTick = useCallback(() => { console.log(count); // ❌ 永远打印 0,闭包陷阱 setCount(count + 1); }, []); // ❌ 空依赖 }修复方案一:把依赖加上
// ✅ 加上依赖 const handleTick = useCallback(() => { console.log(count); setCount(count + 1); }, [count]); // ✅ count 变化时更新修复方案二:用函数式更新
// ✅ 函数式更新,不需要 count 依赖 const handleTick = useCallback(() => { setCount(prev => prev + 1); // ✅ 不依赖外部 count }, []);⑤ useMemo 缓存对象,但依赖项是引用不稳定的
// ❌ 依赖项是每次新建的对象 → useMemo 每次都重新计算 function Search({ query }) { const params = { keyword: query, page: 1 }; const results = useMemo(() => { return fetchData(params); // ❌ params 每次新对象 → memo 失效 }, [params]); // ❌ params 引用不稳定 }修复:依赖原始值而非对象
// ✅ 依赖原始值,而非对象 function Search({ query }) { const results = useMemo(() => { return fetchData({ keyword: query, page: 1 }); // ✅ 对象在内部创建 }, [query]); // ✅ 只依赖原始值 }三、万能兜底:三个问题判断该不该用
问自己三个问题:
1. 这个函数/对象会被传给子组件吗? ├── 否 → 不需要用 └── 是 → 看第 2 题 2. 子组件用了 memo 吗? ├── 否 → 不需要用 └── 是 → 看第 3 题 3. 这个函数/对象会作为 useEffect 的依赖吗? ├── 否 → 可能不需要用(除非 memo 子组件) └── 是 → 必须用 useCallback/useMemo 稳定引用快速参考表:
| 场景 | 需要 useMemo/useCallback? |
|---|---|
| 原生元素的事件处理 | ❌ 不需要 |
| 简单计算(加减乘除、取属性) | ❌ 不需要 |
| 传给 memo 子组件的 props | ✅ 需要 |
| useEffect 的函数/对象依赖 | ✅ 需要 |
| 大量数据处理(排序、过滤大数据集) | ✅ 需要 |
| 只在当前组件用的函数 | ❌ 不需要 |
| 传给第三方组件的高频回调 | ✅ 可能需要 |
万能模板:
import { useState, useCallback, useMemo, memo } from 'react'; // ✅ 子组件 memo const ExpensiveChild = memo(({ data, onUpdate }) => { return <div>{data.map(item => <div key={item.id}>{item.name}</div>)}</div>; }); function Parent() { const [keyword, setKeyword] = useState(''); const [rawData, setRawData] = useState([]); // ✅ 传给 memo 子组件 → 用 useCallback const handleUpdate = useCallback((id) => { setRawData(prev => prev.map(item => item.id === id ? { ...item, name: 'updated' } : item )); }, []); // ✅ 大量数据过滤 → 用 useMemo const filteredData = useMemo(() => { return rawData.filter(item => item.name.includes(keyword)); }, [rawData, keyword]); // ✅ 依赖原始值 return ( <> <input value={keyword} onChange={e => setKeyword(e.target.value)} /> <ExpensiveChild data={filteredData} onUpdate={handleUpdate} /> </> ); }四、预防 checklist
- 不要无脑包:原生元素的事件、简单计算不需要 useMemo/useCallback
- memo 子组件的 props 必须稳定:传函数用 useCallback,传对象用 useMemo
- useEffect 的依赖是函数/对象时:必须用 useCallback/useMemo 稳定引用
- 依赖数组写原始值:别把引用不稳定的对象放进依赖数组
- 闭包陷阱:useCallback 里用了 state 就要加依赖,或用函数式更新
- 先写对再优化:先让代码跑起来,出现不必要的重渲染再加 memo + useCallback
- useCallback 的依赖和 useMemo 一样:变了就重建,没变就复用
五、一句话总结
「useMemo / useCallback 解决的是引用稳定性,不是性能」。只有传给 memo 子组件的 props 或作为 effect 依赖时才需要,其他场景直接定义。先写对,再优化,别上来就全包一层!
最后问候亲爱的朋友们,并邀请你们阅读我的全新著作
📚 《React 进阶实战》
