理解 React 的 `useMemo` Hook
·
理解 React 的 useMemo Hook
什么是 useMemo?
useMemo 是一个 React Hook,用于记忆化(缓存)昂贵计算的结果。它只在依赖项发生变化时重新计算值,从而避免在每次渲染时进行不必要的重新计算。
语法
const memoizedValue = useMemo(() => {
// 昂贵的计算
return computeExpensiveValue(a, b);
}, [a, b]); // 依赖项数组
何时使用 useMemo
- 昂贵的计算 - 需要时间的复杂计算
- 派生值 - 从 props/state 计算得出的值
- 引用相等性 - 当你需要相同的对象/数组引用时
- 性能优化 - 防止子组件不必要的重新渲染
何时不使用 useMemo
- 简单计算(开销不值得)
- 无论如何每次渲染都会改变的值
- 过早优化(只在有性能问题时才优化)
代码示例
示例 1: 昂贵的计算
import { useMemo, useState } from 'react';
function ExpensiveComponent({ items }: { items: number[] }) {
const [filter, setFilter] = useState('');
// ❌ 不好:每次渲染都重新计算
const expensiveResult = items
.filter(item => item > 100)
.map(item => item * 2)
.reduce((sum, item) => sum + item, 0);
// ✅ 好:只在 items 改变时重新计算
const expensiveResult = useMemo(() => {
console.log('正在计算昂贵的结果...');
return items
.filter(item => item > 100)
.map(item => item * 2)
.reduce((sum, item) => sum + item, 0);
}, [items]);
return (
<div>
<input
value={filter}
onChange={(e) => setFilter(e.target.value)}
/>
<p>结果: {expensiveResult}</p>
</div>
);
}
示例 2: 派生值
// 来自你的 main.tsx - 这是一个很好的用例!
const doubleColumnMode = useMemo(
() => openResearchId !== null,
[openResearchId],
);
// 这确保了 doubleColumnMode 只在 openResearchId 改变时改变
// 尽管计算很简单,但它对引用稳定性很有好处
示例 3: 过滤和排序大型列表
import { useMemo, useState } from 'react';
function UserList({ users }: { users: User[] }) {
const [searchTerm, setSearchTerm] = useState('');
const [sortBy, setSortBy] = useState<'name' | 'email'>('name');
// ✅ 记忆化的过滤和排序列表
const filteredAndSortedUsers = useMemo(() => {
console.log('正在过滤和排序用户...');
let result = users.filter(user =>
user.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
user.email.toLowerCase().includes(searchTerm.toLowerCase())
);
result.sort((a, b) => {
if (sortBy === 'name') {
return a.name.localeCompare(b.name);
}
return a.email.localeCompare(b.email);
});
return result;
}, [users, searchTerm, sortBy]);
return (
<div>
<input
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="搜索用户..."
/>
<select value={sortBy} onChange={(e) => setSortBy(e.target.value as 'name' | 'email')}>
<option value="name">按姓名排序</option>
<option value="email">按邮箱排序</option>
</select>
<ul>
{filteredAndSortedUsers.map(user => (
<li key={user.id}>{user.name} - {user.email}</li>
))}
</ul>
</div>
);
}
示例 4: 对象/数组属性的引用相等性
import { useMemo } from 'react';
function ParentComponent({ items }: { items: number[] }) {
const [count, setCount] = useState(0);
// ❌ 不好:每次渲染都创建新对象
// ChildComponent 会不必要地重新渲染
const config = {
items: items,
theme: 'dark'
};
// ✅ 好:除非 items 改变,否则保持相同的对象引用
const config = useMemo(() => ({
items: items,
theme: 'dark'
}), [items]);
return (
<div>
<button onClick={() => setCount(count + 1)}>计数: {count}</button>
<ChildComponent config={config} />
</div>
);
}
// ChildComponent 使用 React.memo 来防止不必要的重新渲染
const ChildComponent = React.memo(({ config }: { config: Config }) => {
console.log('ChildComponent 已渲染');
return <div>{config.items.length} 项</div>;
});
示例 5: 复杂的数据转换
import { useMemo } from 'react';
function Dashboard({ transactions }: { transactions: Transaction[] }) {
// ✅ 记忆化的统计计算
const statistics = useMemo(() => {
const total = transactions.reduce((sum, t) => sum + t.amount, 0);
const average = total / transactions.length || 0;
const categories = transactions.reduce((acc, t) => {
acc[t.category] = (acc[t.category] || 0) + t.amount;
return acc;
}, {} as Record<string, number>);
const topCategory = Object.entries(categories)
.sort(([, a], [, b]) => b - a)[0]?.[0] || 'None';
return {
total,
average,
count: transactions.length,
topCategory,
categories
};
}, [transactions]);
return (
<div>
<h2>统计信息</h2>
<p>总计: ${statistics.total.toFixed(2)}</p>
<p>平均: ${statistics.average.toFixed(2)}</p>
<p>交易数: {statistics.count}</p>
<p>顶级分类: {statistics.topCategory}</p>
</div>
);
}
示例 6: 格式化日期或数字
import { useMemo } from 'react';
function ProductCard({ product, locale }: { product: Product; locale: string }) {
// ✅ 记忆化的格式化值
const formattedPrice = useMemo(() => {
return new Intl.NumberFormat(locale, {
style: 'currency',
currency: product.currency
}).format(product.price);
}, [product.price, product.currency, locale]);
const formattedDate = useMemo(() => {
return new Intl.DateTimeFormat(locale, {
year: 'numeric',
month: 'long',
day: 'numeric'
}).format(new Date(product.createdAt));
}, [product.createdAt, locale]);
return (
<div>
<h3>{product.name}</h3>
<p>价格: {formattedPrice}</p>
<p>创建时间: {formattedDate}</p>
</div>
);
}
示例 7: 与其他 Hooks 结合使用
import { useMemo, useState, useEffect } from 'react';
function SearchableList({ items }: { items: Item[] }) {
const [search, setSearch] = useState('');
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
// ✅ 记忆化的过滤项
const filteredItems = useMemo(() => {
return items.filter(item => {
const matchesSearch = item.name.toLowerCase().includes(search.toLowerCase());
const matchesCategory = !selectedCategory || item.category === selectedCategory;
return matchesSearch && matchesCategory;
});
}, [items, search, selectedCategory]);
// ✅ 记忆化的唯一分类
const categories = useMemo(() => {
return Array.from(new Set(items.map(item => item.category)));
}, [items]);
return (
<div>
<input
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder="搜索..."
/>
<select
value={selectedCategory || ''}
onChange={(e) => setSelectedCategory(e.target.value || null)}
>
<option value="">所有分类</option>
{categories.map(cat => (
<option key={cat} value={cat}>{cat}</option>
))}
</select>
<ul>
{filteredItems.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
);
}
常见模式
模式 1: 条件派生值
const isEnabled = useMemo(
() => user && user.permissions.includes('admin'),
[user]
);
模式 2: 数组/对象创建
const options = useMemo(
() => items.map(item => ({ label: item.name, value: item.id })),
[items]
);
模式 3: 昂贵的函数调用
const result = useMemo(
() => expensiveFunction(data),
[data]
);
性能提示
- 先进行性能分析 - 使用 React DevTools Profiler 来识别性能瓶颈
- 不要过度使用 -
useMemo本身也有开销 - 包含所有依赖项 - 缺少依赖项可能导致 bug
- 与 React.memo 结合使用 - 一起使用以获得最佳效果
与其他 Hooks 的对比
useMemo- 记忆化一个值useCallback- 记忆化一个函数React.memo- 记忆化一个组件
// useMemo - 用于值
const expensiveValue = useMemo(() => compute(), [deps]);
// useCallback - 用于函数
const handleClick = useCallback(() => {
doSomething();
}, [deps]);
// React.memo - 用于组件
const MemoizedComponent = React.memo(Component);
更多推荐


所有评论(0)