React 性能优化(下):useCallback 与 useTransition 实战
引言
在 React 应用性能优化中,useCallback 和 useTransition 是两个强大但常被误解的 Hook。本文将深入探讨它们的正确使用场景、常见陷阱以及实际代码示例,帮助你写出更高效的 React 应用。
useCallback:避免不必要的函数重建
核心原理
useCallback 返回一个记忆化的回调函数,只有在依赖项变化时才会重新创建。这对于避免子组件不必要的重新渲染至关重要。
基础用法
import React, { useState, useCallback } from 'react';
function ParentComponent() {
const [count, setCount] = useState(0);
const [text, setText] = useState('');
// ❌ 错误:每次渲染都会创建新函数
const handleClick = () => {
console.log('Count:', count);
};
// ✅ 正确:使用 useCallback 记忆化
const handleMemoizedClick = useCallback(() => {
console.log('Count:', count);
}, [count]);
return (
<div>
<button onClick={handleMemoizedClick}>点击</button>
<input value={text} onChange={e => setText(e.target.value)} />
</div>
);
}
配合 React.memo 使用
import React, { useState, useCallback, memo } from 'react';
const ChildComponent = memo(({ onIncrement, value }) => {
console.log('Child rendered');
return (
<button onClick={onIncrement}>
Count: {value}
</button>
);
});
function ParentComponent() {
const [count, setCount] = useState(0);
const [text, setText] = useState('');
// ✅ 只有 count 变化时,onIncrement 才会变化
const handleIncrement = useCallback(() => {
setCount(prev => prev + 1);
}, []);
return (
<div>
<ChildComponent onIncrement={handleIncrement} value={count} />
<input value={text} onChange={e => setText(e.target.value)} />
</div>
);
}
常见陷阱
// ❌ 陷阱 1:依赖项过多导致失去优化效果
const handler = useCallback(() => {
doSomething(a, b, c, d, e);
}, [a, b, c, d, e]); // 几乎每次都会重新创建
// ✅ 解决:只依赖真正需要的变量
const handler = useCallback(() => {
doSomething(a, b);
}, [a, b]);
// ❌ 陷阱 2:在 useCallback 内部使用非稳定引用
const handler = useCallback(() => {
config.doSomething(); // config 每次都是新对象
}, [config]);
// ✅ 解决:依赖稳定的值
const handler = useCallback(() => {
configRef.current.doSomething();
}, []);
useTransition:优化耗时更新
核心概念
useTransition 允许你将某些状态更新标记为"过渡"更新,让 UI 保持响应式,避免阻塞用户交互。
基础用法
import React, { useState, useTransition } from 'react';
function SearchComponent() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [isPending, startTransition] = useTransition();
const handleSearch = (value) => {
setQuery(value);
// ✅ 将耗时的过滤操作标记为过渡更新
startTransition(() => {
const filtered = heavyFilter(value);
setResults(filtered);
});
};
return (
<div>
<input
value={query}
onChange={e => handleSearch(e.target.value)}
placeholder="搜索..."
/>
{isPending && <span>加载中...</span>}
<ul>
{results.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
);
}
function heavyFilter(query) {
// 模拟耗时操作
const data = largeDataset.filter(item =>
item.name.includes(query)
);
return data;
}
实际场景:标签切换
import React, { useState, useTransition } from 'react';
function TabComponent() {
const [activeTab, setActiveTab] = useState('posts');
const [isPending, startTransition] = useTransition();
const tabs = {
posts: <PostsList />,
comments: <CommentsList />,
analytics: <AnalyticsPanel />
};
const handleTabChange = (tab) => {
startTransition(() => {
setActiveTab(tab);
});
};
return (
<div>
<nav>
{Object.keys(tabs).map(tab => (
<button
key={tab}
onClick={() => handleTabChange(tab)}
disabled={isPending}
>
{tab}
</button>
))}
</nav>
{isPending && <div className="spinner">切换中...</div>}
<main>
{tabs[activeTab]}
</main>
</div>
);
}
与 Suspense 配合
import React, { useState, useTransition, Suspense } from 'react';
function Dashboard() {
const [activeView, setActiveView] = useState('overview');
const [isPending, startTransition] = useTransition();
const handleViewChange = (view) => {
startTransition(() => {
setActiveView(view);
});
};
return (
<div>
<TabNav onChange={handleViewChange} active={activeView} />
<Suspense fallback={<LoadingSkeleton />}>
<ViewContent view={activeView} />
</Suspense>
</div>
);
}
性能对比实测
// 未优化的版本
function UnoptimizedList({ items }) {
const [filter, setFilter] = useState('');
// 每次输入都会重新渲染整个列表
const filtered = items.filter(item =>
item.name.includes(filter)
);
return (
<div>
<input onChange={e => setFilter(e.target.value)} />
<List data={filtered} />
</div>
);
}
// 优化后的版本
function OptimizedList({ items }) {
const [filter, setFilter] = useState('');
const [displayItems, setDisplayItems] = useState(items);
const [isPending, startTransition] = useTransition();
const handleChange = (e) => {
const value = e.target.value;
setFilter(value);
startTransition(() => {
const filtered = items.filter(item =>
item.name.includes(value)
);
setDisplayItems(filtered);
});
};
return (
<div>
<input value={filter} onChange={handleChange} />
{isPending && <LoadingIndicator />}
<List data={displayItems} />
</div>
);
}
最佳实践总结
useCallback 使用指南
-
优先优化子组件:只有当函数作为 prop 传递给
React.memo组件时才需要 -
避免过早优化:不是所有函数都需要
useCallback - 注意依赖项:确保依赖项稳定且必要
- 配合 useMemo:复杂计算场景结合使用
useTransition 使用指南
- 识别耗时更新:列表过滤、大数据渲染、复杂计算
-
提供加载反馈:使用
isPending显示加载状态 - 区分优先级:用户输入立即响应,数据更新可延迟
- 避免滥用:简单更新不需要过渡
总结
useCallback 和 useTransition 是 React 性能优化的利器,但需要正确使用:
-
useCallback解决的是函数引用稳定性问题 -
useTransition解决的是更新优先级问题
记住:性能优化应该基于实际测量,而非猜测。使用 React DevTools Profiler 找出真正的瓶颈,再针对性地应用这些 Hook。