useCallback、useMemo、React.memo
本文由 AI 协助更新
本文说明三个与「记忆 / 稳定引用」相关的 API:useCallback、useMemo、React.memo,用于在需要时保持函数或值的引用稳定、或减少重复计算。若你主要关心性能,新版 React 可借助 React Compiler 自动做 memoization,从而减少手写这三者的需要。
useCallback
const memoizedCallback = useCallback(() => {
doSomething(a, b)
}, [a, b])
返回一个在依赖不变时引用稳定的函数。
需要把回调传给经 React.memo 包装的子组件时可用,但在 React Compiler 下,这类场景多数可省略手写。
若函数是作为 useEffect 等 Hook 的依赖,更推荐用 useEffectEvent。
useMemo
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b])
在依赖不变时返回同一引用的结果。
保持对象/数组引用稳定
父组件每次渲染都新建对象或数组时,子组件即使用 React.memo 也会因 props 引用每次都是新的而重渲染。用 useMemo 在对象等同时返回同一引用即可。使用 React Compiler 时,这类场景通常可由编译器自动优化,无需手写 useMemo。
Effect 依赖的引用稳定:避免重复订阅
Effect 依赖「筛选后的列表」做订阅时,若列表每次渲染都是新数组,依赖会每次都变,导致重复订阅/取消。用 useMemo 让列表在 todos、filter 未变时保持同一引用,Effect 只在列表真正变化时执行。
function App() {
const [todos, setTodos] = useState<Todo[]>([])
const [filter, setFilter] = useState('all')
const filteredTodos = useMemo(() => {
if (filter === 'all') return todos
return filter === 'active'
? todos.filter((t) => !t.completed)
: todos.filter((t) => t.completed)
}, [todos, filter])
useEffect(() => {
subscribeToTodos(filteredTodos)
return () => unsubscribeFromTodos(filteredTodos)
}, [filteredTodos])
}
缓存计算结果(引用稳定 + 少算一次)
当派生数据既作为 props 传给 memo 子组件,又确实计算较贵时,useMemo 同时起到「稳定引用」和「避免重复计算」的作用。
const filteredTodos = useMemo(() => {
return todos
.filter((todo) => (filter === 'all' ? true : todo.length > 3))
.sort((a, b) => a.localeCompare(b))
}, [todos, filter])
return filteredTodos.map((todo, index) => <Text key={index}>{todo}</Text>)
子节点/元素引用稳定
需要把「一段 JSX 或子组件」作为稳定引用传给父组件或用于依赖时,也可以用 useMemo 包一层(组件返回的是元素,见 认识组件和元素)。依赖未变时,该子节点不会重新创建。
const activeList = useMemo(() => <TodoList todos={activeTodos} />, [activeTodos])
const completedList = useMemo(() => <TodoList todos={completedTodos} />, [completedTodos])
何时不必用
简单计算(如 todos.length)或不会作为 props 传给 memo 子组件的值,不必为「少算一次」单独加 useMemo,直接计算即可。useMemo 本身有比较依赖的开销,只在「需要稳定引用」或「计算确实贵且依赖不变」时再用。
React.memo
const MemoizedComponent = React.memo(Component)
组件级的「记忆」:对 props 做浅比较,未变则复用上次渲染结果,不重新执行组件函数。与 useCallback / useMemo 配合时,才能发挥最大作用——父组件传给子组件的函数和对象引用稳定,memo 才能跳过重渲染。
| API | 作用对象 | 主要用途 |
|---|---|---|
React.memo | 组件 | props 未变时跳过渲染 |
useMemo | 值(对象/数组等) | 保持值的引用稳定 |
useCallback | 函数 | 保持函数的引用稳定 |
基本用法
const MemoizedTodoItem = React.memo(function TodoItem({ title }: TodoItemProps) {
console.log('TodoItem 渲染')
return <Text style={styles.todoItem}>{title}</Text>
})
function App() {
const [filter, setFilter] = useState('all')
const [todos] = useState(['买菜', '写代码', '运动'])
return (
<View style={styles.container}>
<Text>筛选: {filter}</Text>
<Button title="切换筛选" onPress={() => setFilter(filter === 'all' ? 'active' : 'all')} />
{todos.map((todo, index) => (
<MemoizedTodoItem key={index} title={todo} />
))}
</View>
)
}
filter 变化时,todos 和每个 title 没变,MemoizedTodoItem 不会重渲染。
自定义比较
默认是浅比较 props。需要自定义时,传第二个参数:
const TodoItem = React.memo(
({ todo, onToggle }: TodoItemProps) => { /* ... */ },
(prevProps, nextProps) =>
prevProps.todo.id === nextProps.todo.id &&
prevProps.todo.completed === nextProps.todo.completed
)
返回 true 表示视为相等,不重渲染。
与 useMemo / useCallback 一起用
父组件用 useMemo 稳定对象/数组 props,用 useCallback 稳定回调 props,子组件用 React.memo,才能稳定跳过重渲染。
const TodoItem = React.memo(({ todo, onToggle }: TodoItemProps) => { /* ... */ })
function App() {
const [todos, setTodos] = useState<Todo[]>([...])
const [filter, setFilter] = useState('all')
const filteredTodos = useMemo(() => {
if (filter === 'all') return todos
return filter === 'active' ? todos.filter((t) => !t.completed) : todos.filter((t) => t.completed)
}, [todos, filter])
const handleToggle = useCallback((id: number) => {
setTodos((prev) =>
prev.map((todo) =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
)
)
}, [])
return (
<View style={styles.container}>
<Button title="切换筛选" onPress={() => setFilter(filter === 'all' ? 'active' : 'all')} />
{filteredTodos.map((todo) => (
<TodoItem key={todo.id} todo={todo} onToggle={handleToggle} />
))}
</View>
)
}
不要滥用
只对「渲染成本高、且 props 不经常变」的组件包 React.memo。简单展示型组件不必包。
性能优化与 React Compiler
使用本节 API 时建议:
- 先保证正确性—— 避免昂贵计算,或避免 Effect 依赖抖动,而不是为了「看起来在优化」。
- 不为了「省一次计算」滥用——简单计算或非 props 的值不必包一层;不确定时可以不写,交给 React Compiler 或后续用 Profiler 再优化。
- 优化有成本——依赖比较、缓存都有开销,只在有明确收益时使用。