使用 React.memo、useCallback 与 useMemo 进行记忆化
使用 React.memo 包裹开销较大的子组件,使用 useCallback 稳定回调属性,并使用 useMemo 缓存派生值,以避免多余的渲染。
使用 React.memo、useCallback 与 useMemo 进行记忆化 是 CoddyKit 上的免费 React Native Academy 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 React Native Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 React Native Academy 课程共包含 4 节课。
本课时的部分内容尚未翻译,以英文显示。
Why Components Re-Render
In React, a component re-renders whenever its parent re-renders, its state changes, or its context changes. When a parent re-renders, all child components re-render by default — even if their props have not changed. For a list with 50 items, this means 50 re-renders on every parent state change.
Memoization is the technique of caching the result of a computation and returning the cached result when the inputs have not changed. React provides three memoization tools: React.memo, useCallback, and useMemo.
React.memo for Component Memoization
React.memo is a higher-order component that wraps a functional component and prevents re-renders when the component's props have not changed (using shallow equality comparison). If the parent re-renders but passes the same props, the memoized child is skipped.
Wrap a component with React.memo when it receives stable props and is expensive to render. Row components in a FlatList are the classic use case — they receive the same item data on every parent scroll event.
import React from 'react';
import { View, Text } from 'react-native';
interface PostRowProps {
title: string;
author: string;
}
// Without memo: re-renders on every parent update
// function PostRow({ title, author }: PostRowProps) {
// With memo: skips re-render if title and author are unchanged
const PostRow = React.memo(function PostRow({ title, author }: PostRowProps) {
return (
<View>
<Text>{title}</Text>
<Text>{author}</Text>
</View>
);
});
export default PostRow;React.memo Custom Comparison
By default, React.memo uses a shallow comparison — it checks if each top-level prop is strictly equal (===). For objects and arrays, shallow equality means the reference must be the same, not just the content.
If your component receives an object prop and you want to compare by value rather than reference, pass a custom comparison function as the second argument to React.memo. The function receives the previous and next props and returns true if the component should not re-render.
const PostRow = React.memo(
function PostRow({ post }) {
return <Text>{post.title}</Text>;
},
// Custom comparison: skip re-render if post.id and post.title are the same
(prevProps, nextProps) => {
return (
prevProps.post.id === nextProps.post.id &&
prevProps.post.title === nextProps.post.title
);
}
);useCallback: Stabilizing Function References
When a parent component defines a function inside its render body, that function is recreated on every render, producing a new reference each time. If that function is passed as a prop to a React.memo child, the memo is defeated because the prop reference always changes.
useCallback(fn, deps) memoizes the function reference and only creates a new function when the values in the dependency array change. This keeps the prop reference stable across renders, allowing React.memo to work correctly.
import { useCallback } from 'react';
export function PostList() {
const [posts, setPosts] = useState([]);
// Without useCallback: new function reference on every render
// const handleDelete = (id) => deletePost(id);
// With useCallback: stable reference, memoized child is not re-rendered
const handleDelete = useCallback((id: string) => {
deletePost(id);
setPosts((prev) => prev.filter((p) => p.id !== id));
}, []); // No deps: function never changes
return (
<FlatList
data={posts}
renderItem={({ item }) => (
<PostRow post={item} onDelete={handleDelete} />
)}
/>
);
}useMemo: Caching Expensive Computations
useMemo(fn, deps) memoizes the return value of a function. React runs the function once, caches the result, and returns the cached value on subsequent renders until the dependency array values change.
Use useMemo for expensive operations like sorting or filtering large arrays, transforming API data, or computing derived values from state. Do not use it for cheap computations — the memoization overhead can exceed the benefit.
import { useMemo } from 'react';
export function ContactList({ contacts, searchQuery }) {
// Recomputed only when contacts or searchQuery changes
const filteredContacts = useMemo(() => {
if (!searchQuery) return contacts;
const query = searchQuery.toLowerCase();
return contacts.filter((c) =>
c.name.toLowerCase().includes(query) ||
c.email.toLowerCase().includes(query)
);
}, [contacts, searchQuery]);
return (
<FlatList
data={filteredContacts}
keyExtractor={(item) => item.id}
renderItem={({ item }) => <ContactRow contact={item} />}
/>
);
}Dependency Arrays: The Key to Correctness
Both useCallback and useMemo rely on dependency arrays to know when to recompute. Every variable from the outer scope that is used inside the memoized function must be listed in the dependency array — otherwise the cached value will be stale.
The ESLint plugin eslint-plugin-react-hooks enforces this rule automatically. The exhaustive-deps rule warns if you omit a dependency. Missing deps are the most common source of subtle bugs with memoization.
// WRONG: userId is used inside but not in deps
const fetchPosts = useCallback(async () => {
const posts = await api.getPostsByUser(userId); // stale userId!
setPosts(posts);
}, []); // Missing dependency: userId
// CORRECT: all used external values listed
const fetchPosts = useCallback(async () => {
const posts = await api.getPostsByUser(userId);
setPosts(posts);
}, [userId]); // Recomputed when userId changesMemoizing Context Values
When you provide a Context value, React compares the value reference on every render. If the context value is an object literal created inline, it gets a new reference every render, causing all context consumers to re-render even if no data actually changed.
Wrap the context value in useMemo to stabilize its reference. Split context into separate providers when possible — for example, keep user data and UI theme in separate contexts so a theme change does not re-render all components that consume user data.
const ThemeContext = createContext(null);
export function ThemeProvider({ children }) {
const [isDark, setIsDark] = useState(false);
// Without useMemo: new object reference on every render
// value={{ isDark, toggleTheme: () => setIsDark(d => !d) }}
// With useMemo: stable reference, consumers only re-render when isDark changes
const value = useMemo(() => ({
isDark,
toggleTheme: () => setIsDark((d) => !d),
}), [isDark]);
return (
<ThemeContext.Provider value={value}>
{children}
</ThemeContext.Provider>
);
}When NOT to Memoize
Memoization is not free. useMemo and useCallback run the comparison on every render. For simple components and cheap operations, this overhead can make performance worse, not better.
Avoid memoization when: the component renders infrequently, the computation is trivially fast (like adding two numbers), or the dependencies change on every render (making the memo useless). Apply memoization only where the React Profiler shows a measurable improvement.
// ❌ Over-memoization: computation is trivial, no benefit
const fullName = useMemo(() => {
return firstName + ' ' + lastName;
}, [firstName, lastName]);
// ✅ Better: just compute it
const fullName = firstName + ' ' + lastName;
// ✅ Good use of useMemo: sorting 5000 items is expensive
const sortedItems = useMemo(() => {
return [...items].sort(compareByDate);
}, [items]);The useCallback Pattern for FlatList renderItem
FlatList re-renders item rows when its renderItem prop changes. Defining renderItem as an inline arrow function creates a new reference on every parent render, defeating any memoization on the item component.
Define renderItem with useCallback and add only the dependencies that truly change its behavior. Also memoize the keyExtractor function for the same reason — stable references allow FlatList to optimize its reconciliation.
const renderItem = useCallback(
({ item }) => <PostRow post={item} onDelete={handleDelete} />,
[handleDelete] // handleDelete is already wrapped in useCallback
);
const keyExtractor = useCallback(
(item) => item.id,
[] // Pure function, no deps
);
return (
<FlatList
data={posts}
renderItem={renderItem}
keyExtractor={keyExtractor}
/>
);Using the Profiler to Verify Memoization Works
After applying memoization, use the React Profiler to verify it is actually working. Record the same interaction and compare the flame graph before and after. Memoized components should appear grayed out in the profiler (indicating they were skipped) rather than colored bars (indicating they rendered).
If a memoized component still shows up as rendered, check the Why did this render? panel in DevTools. It will tell you exactly which prop or hook changed, revealing an unstable reference or a missing memoization step.
Summary: When to Use Each Tool
A quick decision guide for React memoization tools:
- React.memo — wrap a component whose parent re-renders often but whose own props change rarely
- useCallback — stabilize a function reference passed as a prop to a memoized child, or used as a dependency of another hook
- useMemo — cache an expensive derived value (filtering, sorting, transforming large arrays) that is recomputed on every render
Always profile first to confirm there is a real problem, apply one optimization at a time, and measure again to confirm the improvement.
Quick Check
Test your understanding of React Native Mobile Development concepts from this lesson.
Lesson Recap
In this lesson you learned: how React.memo prevents child re-renders when props have not changed, how useCallback stabilizes function references to make React.memo effective, and how useMemo caches expensive computed values across renders. Next up we tune FlatList rendering performance for smooth list scrolling.
常见问题解答
「使用 React.memo、useCallback 与 useMemo 进行记忆化」课时是免费的吗?
是的 — 「使用 React.memo、useCallback 与 useMemo 进行记忆化」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 React Native Academy 课程的其余内容,请升级到 CoddyKit PRO。 React Native Academy 课程共包含 4 节课。
「使用 React.memo、useCallback 与 useMemo 进行记忆化」这节课中我会学到什么?
使用 React.memo 包裹开销较大的子组件,使用 useCallback 稳定回调属性,并使用 useMemo 缓存派生值,以避免多余的渲染。 你通过在浏览器中直接运行的动手代码来练习 React Native Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 React Native Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 React Native Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。
「使用 React.memo、useCallback 与 useMemo 进行记忆化」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 React Native Academy 课中编写并运行代码吗?
能。每节 React Native Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- 使用 Flipper 与 React DevTools 进行性能分析
- 使用 React.memo、useCallback 与 useMemo 进行记忆化
- FlatList 性能调优
- 包体积与延迟加载