
React 性能优化
先进行性能分析
在优化之前始终先进行性能分析。使用 React DevTools Profiler 查找缓慢的渲染。
// 使用 Profiler 包裹以测量
import { Profiler } from 'react';
<Profiler
id="UserList"
onRender={(id, phase, actualDuration) => {
if (actualDuration > 16) { // 慢于 60fps
console.warn(`${id} took ${actualDuration}ms in ${phase}`);
}
}}
>
<UserList />
</Profiler>

React.memo
当 props 未变化时阻止重新渲染。
// 不使用 memo:父组件渲染时总是重新渲染
function UserCard({ user }: { user: User }) {
return <div>{user.name}</div>;
}
// 使用 memo:仅当 user prop 变化时重新渲染
const UserCard = React.memo(({ user }: { user: User }) => {
return <div>{user.name}</div>;
});
// 自定义比较
const UserCard = React.memo(
({ user }: { user: User }) => <div>{user.name}</div>,
(prev, next) => prev.user.id === next.user.id && prev.user.name === next.user.name
);
useMemo - 记忆化昂贵计算
function UserList({ users, searchQuery }: Props) {
// 不使用 useMemo:每次渲染都进行过滤
// const filtered = users.filter(u => u.name.includes(searchQuery));
// 使用 useMemo:仅当 users 或 searchQuery 变化时重新计算
const filtered = useMemo(
() => users.filter(u => u.name.toLowerCase().includes(searchQuery.toLowerCase())),
[users, searchQuery]
);
const stats = useMemo(() => ({
total: users.length,
active: users.filter(u => u.active).length,
admins: users.filter(u => u.role === 'admin').length,
}), [users]);
return (
<div>
<Stats {...stats} />
{filtered.map(user => <UserCard key={user.id} user={user} />)}
</div>
);
}

useCallback - 稳定的函数引用
function UserList() {
const [users, setUsers] = useState<User[]>([]);
// 不使用 useCallback:每次渲染都生成新的函数引用
// -> 导致 UserCard 即使 users 未变化也重新渲染
// const handleDelete = (id: string) => setUsers(u => u.filter(user => user.id !== id));
// 使用 useCallback:稳定引用,UserCard 不会不必要地重新渲染
const handleDelete = useCallback((id: string) => {
setUsers(prev => prev.filter(user => user.id !== id));
}, []); // 空依赖:函数永不变化
return (
<>
{users.map(user => (
<UserCard key={user.id} user={user} onDelete={handleDelete} />
))}
</>
);
}
使用 React.lazy 进行代码分割
import { lazy, Suspense } from 'react';
// 仅在需要时加载组件
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Analytics = lazy(() => import('./pages/Analytics'));
const Settings = lazy(() => import('./pages/Settings'));
function App() {
return (
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/analytics" element={<Analytics />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</Suspense>
);
}

长列表虚拟化
import { useVirtualizer } from '@tanstack/react-virtual';
import { useRef } from 'react';
function VirtualUserList({ users }: { users: User[] }) {
const parentRef = useRef<HTMLDivElement>(null);
const virtualizer = useVirtualizer({
count: users.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 72, // 估计行高
overscan: 5,
});
return (
<div ref={parentRef} style={{ height: '600px', overflow: 'auto' }}>
<div style={{ height: `${virtualizer.getTotalSize()}px`, position: 'relative' }}>
{virtualizer.getVirtualItems().map(virtualItem => (
<div
key={virtualItem.key}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: `${virtualItem.size}px`,
transform: `translateY(${virtualItem.start}px)`,
}}
>
<UserCard user={users[virtualItem.index]} />
</div>
))}
</div>
</div>
);
}
使用 useTransition 处理非紧急更新
function SearchPage() {
const [query, setQuery] = useState('');
const [results, setResults] = useState<User[]>([]);
const [isPending, startTransition] = useTransition();
function handleSearch(value: string) {
setQuery(value); // 紧急:立即更新输入框
startTransition(() => {
// 非紧急:如果用户仍在输入,可以延迟
setResults(filterUsers(value));
});
}
return (
<div>
<input value={query} onChange={e => handleSearch(e.target.value)} />
{isPending ? <Spinner /> : <UserList users={results} />}
</div>
);
}
过早优化是万恶之源。先进行性能分析,然后优化正确的地方。