Appearance
useTransition过渡更新
第一部分:useTransition基础
1.1 什么是useTransition
useTransition是React 18引入的Hook,用于标记非紧急的状态更新。它允许你将某些更新标记为"过渡",这些更新可以被更高优先级的更新打断。
基本语法:
javascript
import { useTransition } from 'react';
function Component() {
const [isPending, startTransition] = useTransition();
// isPending: 布尔值,表示是否有pending的transition
// startTransition: 函数,用于标记transition更新
const handleUpdate = () => {
startTransition(() => {
// 这里的状态更新是低优先级的
setState(newValue);
});
};
return (
<div>
{isPending && <Spinner />}
<button onClick={handleUpdate}>Update</button>
</div>
);
}1.2 为什么需要useTransition
问题场景:
javascript
// 问题:所有更新同等优先级
function SearchPage() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const handleInput = (e) => {
const value = e.target.value;
// 用户输入 - 应该立即响应
setQuery(value);
// 搜索结果 - 耗时操作
setResults(performHeavySearch(value));
// 问题:两个更新同时执行,导致输入框卡顿
};
return (
<div>
<input value={query} onChange={handleInput} />
<SearchResults results={results} />
</div>
);
}useTransition的解决方案:
javascript
// 使用useTransition区分优先级
function SearchPage() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [isPending, startTransition] = useTransition();
const handleInput = (e) => {
const value = e.target.value;
// 高优先级:立即更新输入框
setQuery(value);
// 低优先级:可中断的搜索
startTransition(() => {
setResults(performHeavySearch(value));
});
};
return (
<div>
<input value={query} onChange={handleInput} />
{isPending && <SearchingIndicator />}
<SearchResults results={results} />
</div>
);
}1.3 useTransition的工作原理
javascript
// useTransition内部机制(简化)
function useTransition() {
const [isPending, setIsPending] = useState(false);
const startTransition = useCallback((callback) => {
setIsPending(true);
// 1. 标记为低优先级更新
scheduleTransitionUpdate(() => {
callback();
setIsPending(false);
});
}, []);
return [isPending, startTransition];
}
// 调度transition更新
function scheduleTransitionUpdate(callback) {
const previousPriority = getCurrentPriority();
try {
// 设置为transition优先级
setCurrentPriority(TransitionPriority);
callback();
} finally {
// 恢复之前的优先级
setCurrentPriority(previousPriority);
}
}第二部分:startTransition API
2.1 基本用法
javascript
// 1. 基本transition
function Component() {
const [isPending, startTransition] = useTransition();
const [data, setData] = useState([]);
const updateData = () => {
startTransition(() => {
setData(computeExpensiveData());
});
};
return (
<div>
<button onClick={updateData} disabled={isPending}>
{isPending ? 'Updating...' : 'Update'}
</button>
<DataList items={data} />
</div>
);
}
// 2. 多个状态更新
function MultiUpdate() {
const [isPending, startTransition] = useTransition();
const [count, setCount] = useState(0);
const [list, setList] = useState([]);
const handleUpdate = () => {
startTransition(() => {
// 多个更新都是transition
setCount(c => c + 1);
setList(generateLargeList());
});
};
return (
<div>
<button onClick={handleUpdate}>Update</button>
{isPending && <Loading />}
<div>Count: {count}</div>
<List items={list} />
</div>
);
}
// 3. 嵌套transition
function NestedTransition() {
const [isPending, startTransition] = useTransition();
const handleAction = () => {
startTransition(() => {
updateState1();
// 嵌套的transition会合并
startTransition(() => {
updateState2();
});
});
};
}
// 4. 条件transition
function ConditionalTransition() {
const [isPending, startTransition] = useTransition();
const [needsTransition, setNeedsTransition] = useState(false);
const handleUpdate = (value) => {
if (needsTransition) {
startTransition(() => {
setState(value);
});
} else {
setState(value);
}
};
}2.2 startTransition vs setTimeout
javascript
// setTimeout的问题
function WithTimeout() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const handleInput = (e) => {
const value = e.target.value;
setQuery(value);
// 问题:固定延迟,不智能
setTimeout(() => {
setResults(search(value));
}, 300);
};
}
// startTransition的优势
function WithTransition() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [isPending, startTransition] = useTransition();
const handleInput = (e) => {
const value = e.target.value;
setQuery(value);
// 优势:
// 1. 智能调度,不是固定延迟
// 2. 可以被打断
// 3. 与React调度器集成
startTransition(() => {
setResults(search(value));
});
};
}
// 对比演示
function Comparison() {
const [method, setMethod] = useState('transition');
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [isPending, startTransition] = useTransition();
const handleInput = (e) => {
const value = e.target.value;
setQuery(value);
if (method === 'setTimeout') {
setTimeout(() => {
setResults(search(value));
}, 300);
} else {
startTransition(() => {
setResults(search(value));
});
}
};
return (
<div>
<select value={method} onChange={e => setMethod(e.target.value)}>
<option value="setTimeout">setTimeout</option>
<option value="transition">Transition</option>
</select>
<input value={query} onChange={handleInput} />
{method === 'transition' && isPending && <Spinner />}
<Results data={results} />
</div>
);
}2.3 独立的startTransition
javascript
// 不使用Hook的startTransition
import { startTransition } from 'react';
// 在事件处理器外使用
function Component() {
useEffect(() => {
startTransition(() => {
// 可以在任何地方使用
updateState();
});
}, []);
}
// 在类组件中使用
class ClassComponent extends React.Component {
handleClick = () => {
startTransition(() => {
this.setState({ data: newData });
});
};
render() {
return <button onClick={this.handleClick}>Update</button>;
}
}
// 在工具函数中使用
function updateGlobalState(value) {
startTransition(() => {
globalStore.setState(value);
});
}
// 区别:没有isPending状态
// useTransition: 返回isPending状态
// startTransition: 仅执行transition,无状态2.4 配置选项
javascript
// timeoutMs选项(实验性)
function Component() {
const [isPending, startTransition] = useTransition({
timeoutMs: 3000
});
const handleAction = () => {
startTransition(() => {
// 如果3秒内未完成,强制提交
performLongTask();
});
};
}
// 自定义超时处理
function CustomTimeout() {
const [isPending, startTransition] = useTransition();
const timeoutRef = useRef(null);
const handleAction = () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
startTransition(() => {
performTask();
});
// 自定义超时
timeoutRef.current = setTimeout(() => {
if (isPending) {
console.warn('Transition timeout');
}
}, 5000);
};
useEffect(() => {
if (!isPending && timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
}, [isPending]);
}第三部分:isPending状态
3.1 使用isPending
javascript
// 1. 显示加载状态
function LoadingIndicator() {
const [isPending, startTransition] = useTransition();
const [data, setData] = useState([]);
const loadData = () => {
startTransition(() => {
setData(fetchData());
});
};
return (
<div>
<button onClick={loadData} disabled={isPending}>
Load Data
</button>
{isPending && <Spinner />}
<div className={isPending ? 'loading' : ''}>
<DataList items={data} />
</div>
</div>
);
}
// 2. 禁用UI
function DisableUI() {
const [isPending, startTransition] = useTransition();
return (
<div>
<button disabled={isPending}>
{isPending ? 'Processing...' : 'Submit'}
</button>
<input disabled={isPending} />
<div style={{ opacity: isPending ? 0.5 : 1 }}>
Content
</div>
</div>
);
}
// 3. 条件渲染
function ConditionalRender() {
const [isPending, startTransition] = useTransition();
const [results, setResults] = useState([]);
return (
<div>
{isPending ? (
<div>
<Skeleton />
<Skeleton />
<Skeleton />
</div>
) : (
<Results data={results} />
)}
</div>
);
}
// 4. 动画效果
function AnimatedTransition() {
const [isPending, startTransition] = useTransition();
return (
<div className={isPending ? 'fade-out' : 'fade-in'}>
<Content />
</div>
);
}
// CSS
/*
.fade-out {
opacity: 0.5;
transition: opacity 0.3s;
}
.fade-in {
opacity: 1;
transition: opacity 0.3s;
}
*/3.2 多个Transition的isPending
javascript
// 同时有多个transition
function MultipleTransitions() {
const [isPending1, startTransition1] = useTransition();
const [isPending2, startTransition2] = useTransition();
const action1 = () => {
startTransition1(() => {
updateState1();
});
};
const action2 = () => {
startTransition2(() => {
updateState2();
});
};
return (
<div>
<button onClick={action1} disabled={isPending1}>
Action 1 {isPending1 && '...'}
</button>
<button onClick={action2} disabled={isPending2}>
Action 2 {isPending2 && '...'}
</button>
</div>
);
}
// 全局pending状态
function GlobalPending() {
const [isPending1, startTransition1] = useTransition();
const [isPending2, startTransition2] = useTransition();
const isAnyPending = isPending1 || isPending2;
return (
<div>
{isAnyPending && <GlobalSpinner />}
<button onClick={() => startTransition1(() => action1())}>
Action 1
</button>
<button onClick={() => startTransition2(() => action2())}>
Action 2
</button>
</div>
);
}3.3 isPending的时序
javascript
// isPending的变化时序
function PendingTiming() {
const [isPending, startTransition] = useTransition();
useEffect(() => {
console.log('isPending changed:', isPending);
}, [isPending]);
const handleAction = () => {
console.log('Before transition, isPending:', isPending); // false
startTransition(() => {
console.log('Inside transition, isPending:', isPending); // false (还没更新)
performAction();
});
console.log('After startTransition, isPending:', isPending); // false (同步代码中还是false)
// 下一个tick,isPending变为true
setTimeout(() => {
console.log('Next tick, isPending:', isPending); // true
}, 0);
};
// 执行顺序:
// 1. "Before transition, isPending: false"
// 2. "After startTransition, isPending: false"
// 3. "isPending changed: true"
// 4. "Inside transition, isPending: false"
// 5. "Next tick, isPending: true"
// 6. "isPending changed: false"
}第四部分:实战案例
4.1 搜索功能
javascript
// 完整的搜索实现
function SearchComponent() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [isPending, startTransition] = useTransition();
const handleSearch = (value) => {
// 立即更新输入框
setQuery(value);
// 延迟更新搜索结果
startTransition(() => {
if (value.trim() === '') {
setResults([]);
return;
}
// 模拟耗时搜索
const filtered = largeDataset.filter(item =>
item.title.toLowerCase().includes(value.toLowerCase()) ||
item.description.toLowerCase().includes(value.toLowerCase())
);
setResults(filtered);
});
};
return (
<div className="search-container">
<div className="search-input-wrapper">
<input
type="text"
value={query}
onChange={(e) => handleSearch(e.target.value)}
placeholder="搜索..."
className="search-input"
/>
{isPending && (
<div className="search-spinner">
<Spinner size="small" />
</div>
)}
</div>
<div className={`results ${isPending ? 'results-pending' : ''}`}>
{results.length === 0 && query ? (
<div className="no-results">未找到结果</div>
) : (
<SearchResults items={results} />
)}
</div>
</div>
);
}
// 高级搜索:带历史记录
function AdvancedSearch() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [history, setHistory] = useState([]);
const [isPending, startTransition] = useTransition();
const handleSearch = (value) => {
setQuery(value);
startTransition(() => {
const searchResults = performSearch(value);
setResults(searchResults);
// 更新历史记录
if (value && searchResults.length > 0) {
setHistory(prev => {
const newHistory = [value, ...prev.filter(q => q !== value)];
return newHistory.slice(0, 10); // 保留最近10条
});
}
});
};
const selectFromHistory = (historicalQuery) => {
handleSearch(historicalQuery);
};
return (
<div>
<input
value={query}
onChange={(e) => handleSearch(e.target.value)}
/>
{query === '' && history.length > 0 && (
<div className="search-history">
<h3>搜索历史</h3>
{history.map(q => (
<div key={q} onClick={() => selectFromHistory(q)}>
{q}
</div>
))}
</div>
)}
{isPending && <Loading />}
<Results data={results} />
</div>
);
}4.2 Tab切换
javascript
// Tab切换优化
function TabContainer() {
const [activeTab, setActiveTab] = useState('overview');
const [isPending, startTransition] = useTransition();
const switchTab = (tab) => {
startTransition(() => {
setActiveTab(tab);
});
};
return (
<div className="tabs">
<div className="tab-header">
<TabButton
active={activeTab === 'overview'}
onClick={() => switchTab('overview')}
>
概览
</TabButton>
<TabButton
active={activeTab === 'details'}
onClick={() => switchTab('details')}
>
详情
</TabButton>
<TabButton
active={activeTab === 'settings'}
onClick={() => switchTab('settings')}
>
设置
</TabButton>
</div>
{isPending && <TabLoadingBar />}
<div className="tab-content">
{activeTab === 'overview' && <OverviewTab />}
{activeTab === 'details' && <DetailsTab />}
{activeTab === 'settings' && <SettingsTab />}
</div>
</div>
);
}
// 带预加载的Tab
function PreloadingTabs() {
const [activeTab, setActiveTab] = useState('home');
const [preloadedTabs, setPreloadedTabs] = useState(new Set(['home']));
const [isPending, startTransition] = useTransition();
const handleTabHover = (tab) => {
if (!preloadedTabs.has(tab)) {
startTransition(() => {
preloadTabData(tab);
setPreloadedTabs(prev => new Set([...prev, tab]));
});
}
};
const handleTabClick = (tab) => {
startTransition(() => {
setActiveTab(tab);
});
};
return (
<div>
<Tabs
active={activeTab}
onHover={handleTabHover}
onClick={handleTabClick}
/>
{isPending && <ProgressIndicator />}
<TabContent tab={activeTab} />
</div>
);
}4.3 数据过滤
javascript
// 复杂数据过滤
function DataFilter() {
const [items] = useState(generateLargeDataset(10000));
const [filters, setFilters] = useState({
category: 'all',
priceRange: [0, 1000],
search: ''
});
const [filteredItems, setFilteredItems] = useState(items);
const [isPending, startTransition] = useTransition();
const applyFilters = (newFilters) => {
setFilters(newFilters);
startTransition(() => {
const filtered = items.filter(item => {
// 分类过滤
if (newFilters.category !== 'all' &&
item.category !== newFilters.category) {
return false;
}
// 价格过滤
if (item.price < newFilters.priceRange[0] ||
item.price > newFilters.priceRange[1]) {
return false;
}
// 搜索过滤
if (newFilters.search &&
!item.name.toLowerCase().includes(newFilters.search.toLowerCase())) {
return false;
}
return true;
});
setFilteredItems(filtered);
});
};
return (
<div className="filter-container">
<div className="filters">
<select
value={filters.category}
onChange={(e) => applyFilters({ ...filters, category: e.target.value })}
>
<option value="all">所有分类</option>
<option value="electronics">电子产品</option>
<option value="clothing">服装</option>
<option value="books">图书</option>
</select>
<PriceRangeSlider
value={filters.priceRange}
onChange={(range) => applyFilters({ ...filters, priceRange: range })}
/>
<input
type="text"
value={filters.search}
onChange={(e) => applyFilters({ ...filters, search: e.target.value })}
placeholder="搜索商品..."
/>
</div>
{isPending && (
<div className="filter-loading">
<Spinner />
<span>正在过滤 {items.length} 个项目...</span>
</div>
)}
<div className={isPending ? 'items-loading' : 'items'}>
<div className="results-count">
找到 {filteredItems.length} 个结果
</div>
<ItemList items={filteredItems} />
</div>
</div>
);
}4.4 分页加载
javascript
// 分页组件
function PaginatedList() {
const [page, setPage] = useState(1);
const [items, setItems] = useState([]);
const [isPending, startTransition] = useTransition();
const itemsPerPage = 50;
useEffect(() => {
// 初始加载
setItems(fetchItems(1, itemsPerPage));
}, []);
const goToPage = (newPage) => {
startTransition(() => {
setPage(newPage);
const newItems = fetchItems(newPage, itemsPerPage);
setItems(newItems);
});
};
const nextPage = () => goToPage(page + 1);
const prevPage = () => goToPage(page - 1);
return (
<div>
{isPending && <LoadingOverlay />}
<div className={isPending ? 'content-loading' : 'content'}>
<ItemList items={items} />
</div>
<div className="pagination">
<button
onClick={prevPage}
disabled={page === 1 || isPending}
>
上一页
</button>
<span>第 {page} 页</span>
<button
onClick={nextPage}
disabled={isPending}
>
下一页
</button>
</div>
</div>
);
}
// 无限滚动
function InfiniteScroll() {
const [items, setItems] = useState([]);
const [page, setPage] = useState(1);
const [hasMore, setHasMore] = useState(true);
const [isPending, startTransition] = useTransition();
const observerRef = useRef();
const loadMore = useCallback(() => {
if (!hasMore || isPending) return;
startTransition(() => {
const newItems = fetchItems(page + 1);
if (newItems.length === 0) {
setHasMore(false);
} else {
setItems(prev => [...prev, ...newItems]);
setPage(p => p + 1);
}
});
}, [page, hasMore, isPending]);
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
if (entries[0].isIntersecting) {
loadMore();
}
},
{ threshold: 0.1 }
);
if (observerRef.current) {
observer.observe(observerRef.current);
}
return () => observer.disconnect();
}, [loadMore]);
return (
<div>
<ItemList items={items} />
<div ref={observerRef} className="load-more-trigger">
{isPending && <Spinner />}
{!hasMore && <div>没有更多了</div>}
</div>
</div>
);
}4.5 表单优化
javascript
// 表单实时验证
function SmartForm() {
const [formData, setFormData] = useState({
username: '',
email: '',
password: ''
});
const [errors, setErrors] = useState({});
const [isPending, startTransition] = useTransition();
const handleChange = (field, value) => {
// 立即更新表单值
setFormData(prev => ({ ...prev, [field]: value }));
// 延迟验证
startTransition(() => {
const fieldErrors = validateField(field, value, formData);
setErrors(prev => ({ ...prev, [field]: fieldErrors }));
});
};
const handleSubmit = (e) => {
e.preventDefault();
startTransition(() => {
const allErrors = validateForm(formData);
setErrors(allErrors);
if (Object.keys(allErrors).length === 0) {
submitForm(formData);
}
});
};
return (
<form onSubmit={handleSubmit}>
<div>
<input
type="text"
value={formData.username}
onChange={(e) => handleChange('username', e.target.value)}
placeholder="用户名"
/>
{errors.username && <span className="error">{errors.username}</span>}
</div>
<div>
<input
type="email"
value={formData.email}
onChange={(e) => handleChange('email', e.target.value)}
placeholder="邮箱"
/>
{errors.email && <span className="error">{errors.email}</span>}
</div>
<div>
<input
type="password"
value={formData.password}
onChange={(e) => handleChange('password', e.target.value)}
placeholder="密码"
/>
{errors.password && <span className="error">{errors.password}</span>}
</div>
<button type="submit" disabled={isPending}>
{isPending ? '提交中...' : '提交'}
</button>
</form>
);
}第五部分:性能优化
5.1 优化Transition边界
javascript
// ✅ 正确:只包裹耗时操作
function Good() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [isPending, startTransition] = useTransition();
const handleSearch = (value) => {
setQuery(value); // 立即执行,不在transition中
startTransition(() => {
setResults(heavySearch(value)); // 耗时操作
});
};
}
// ❌ 错误:包裹了所有更新
function Bad() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [isPending, startTransition] = useTransition();
const handleSearch = (value) => {
startTransition(() => {
setQuery(value); // 不应该在这里
setResults(heavySearch(value));
});
};
}
// 复杂场景的优化
function Optimized() {
const [searchQuery, setSearchQuery] = useState('');
const [filterQuery, setFilterQuery] = useState('');
const [results, setResults] = useState([]);
const [isPending, startTransition] = useTransition();
const handleSearch = (value) => {
setSearchQuery(value); // 立即
startTransition(() => {
const filtered = items
.filter(item => item.name.includes(value))
.filter(item => filterQuery ? item.category === filterQuery : true);
setResults(filtered);
});
};
const handleFilter = (value) => {
setFilterQuery(value); // 立即
startTransition(() => {
const filtered = items
.filter(item => searchQuery ? item.name.includes(searchQuery) : true)
.filter(item => item.category === value);
setResults(filtered);
});
};
}5.2 配合useMemo使用
javascript
// 结合useMemo优化
function OptimizedComponent() {
const [query, setQuery] = useState('');
const [category, setCategory] = useState('all');
const [isPending, startTransition] = useTransition();
// 使用useMemo缓存计算
const filteredItems = useMemo(() => {
let items = allItems;
if (category !== 'all') {
items = items.filter(item => item.category === category);
}
if (query) {
items = items.filter(item =>
item.name.toLowerCase().includes(query.toLowerCase())
);
}
return items;
}, [query, category]);
const handleSearch = (value) => {
startTransition(() => {
setQuery(value);
});
};
return (
<div>
<input value={query} onChange={e => handleSearch(e.target.value)} />
<CategoryFilter value={category} onChange={setCategory} />
{isPending && <Loading />}
<ItemList items={filteredItems} />
</div>
);
}5.3 避免过度使用
javascript
// ❌ 不必要的transition
function Unnecessary() {
const [count, setCount] = useState(0);
const [isPending, startTransition] = useTransition();
const increment = () => {
// 简单计数不需要transition
startTransition(() => {
setCount(c => c + 1);
});
};
}
// ✅ 只在必要时使用
function Necessary() {
const [count, setCount] = useState(0);
const [heavyData, setHeavyData] = useState([]);
const [isPending, startTransition] = useTransition();
const increment = () => {
setCount(c => c + 1); // 简单操作,不用transition
};
const updateData = () => {
startTransition(() => {
setHeavyData(computeHeavyData()); // 耗时操作用transition
});
};
}
// 判断是否需要transition
function shouldUseTransition(operation) {
// 规则:
// 1. 操作耗时 > 100ms
// 2. 不是用户直接交互的结果
// 3. UI可以延迟显示
const estimatedTime = measureOperationTime(operation);
return estimatedTime > 100;
}第六部分:常见模式
6.1 乐观更新
javascript
// 乐观更新模式
function OptimisticUpdate() {
const [items, setItems] = useState([]);
const [isPending, startTransition] = useTransition();
const addItem = async (newItem) => {
// 立即显示
const optimisticItem = { ...newItem, id: Date.now(), pending: true };
setItems(prev => [...prev, optimisticItem]);
try {
// 后台保存
startTransition(async () => {
const savedItem = await saveItem(newItem);
// 更新为真实数据
setItems(prev =>
prev.map(item =>
item.id === optimisticItem.id ? savedItem : item
)
);
});
} catch (error) {
// 失败时回滚
setItems(prev =>
prev.filter(item => item.id !== optimisticItem.id)
);
showError('保存失败');
}
};
return (
<div>
<ItemList items={items} />
<AddItemForm onAdd={addItem} />
{isPending && <SavingIndicator />}
</div>
);
}6.2 竞态条件处理
javascript
// 处理竞态条件
function RaceConditionHandling() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [isPending, startTransition] = useTransition();
const latestQueryRef = useRef('');
const handleSearch = (value) => {
setQuery(value);
latestQueryRef.current = value;
startTransition(async () => {
const searchResults = await performSearch(value);
// 只使用最新查询的结果
if (value === latestQueryRef.current) {
setResults(searchResults);
}
});
};
return (
<div>
<input value={query} onChange={e => handleSearch(e.target.value)} />
{isPending && <Searching />}
<Results data={results} />
</div>
);
}
// 使用AbortController
function AbortableTransition() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [isPending, startTransition] = useTransition();
const controllerRef = useRef(null);
const handleSearch = (value) => {
setQuery(value);
// 取消之前的请求
if (controllerRef.current) {
controllerRef.current.abort();
}
controllerRef.current = new AbortController();
startTransition(async () => {
try {
const results = await fetch(`/api/search?q=${value}`, {
signal: controllerRef.current.signal
}).then(r => r.json());
setResults(results);
} catch (error) {
if (error.name !== 'AbortError') {
console.error('Search error:', error);
}
}
});
};
useEffect(() => {
return () => {
// 清理
if (controllerRef.current) {
controllerRef.current.abort();
}
};
}, []);
}6.3 错误处理
javascript
// Transition中的错误处理
function ErrorHandling() {
const [isPending, startTransition] = useTransition();
const [error, setError] = useState(null);
const [data, setData] = useState(null);
const fetchData = async () => {
setError(null);
startTransition(async () => {
try {
const result = await loadData();
setData(result);
} catch (err) {
setError(err.message);
}
});
};
if (error) {
return (
<div className="error">
<p>错误: {error}</p>
<button onClick={fetchData}>重试</button>
</div>
);
}
return (
<div>
<button onClick={fetchData} disabled={isPending}>
{isPending ? '加载中...' : '加载数据'}
</button>
{data && <DataDisplay data={data} />}
</div>
);
}
// 使用ErrorBoundary
function WithErrorBoundary() {
const [isPending, startTransition] = useTransition();
const [data, setData] = useState(null);
const loadData = () => {
startTransition(() => {
const result = fetchDataThatMightThrow();
setData(result);
});
};
return (
<ErrorBoundary fallback={<ErrorFallback />}>
<button onClick={loadData} disabled={isPending}>
Load
</button>
{data && <DataDisplay data={data} />}
</ErrorBoundary>
);
}注意事项
1. 使用场景
✅ 适合使用transition的场景:
- 搜索/过滤(耗时 > 100ms)
- Tab切换
- 数据可视化更新
- 复杂列表渲染
- 路由导航
❌ 不适合的场景:
- 简单的计数器
- 用户输入反馈
- 表单提交按钮状态
- 关键用户交互2. 常见错误
javascript
// ❌ 在transition中读取最新state
function Wrong() {
const [count, setCount] = useState(0);
const [isPending, startTransition] = useTransition();
const update = () => {
startTransition(() => {
// 错误:这里的count可能不是最新的
setOther(count + 1);
});
};
}
// ✅ 正确的做法
function Correct() {
const [count, setCount] = useState(0);
const [isPending, startTransition] = useTransition();
const update = () => {
const currentCount = count; // 外面读取
startTransition(() => {
setOther(currentCount + 1);
});
};
}3. 性能提示
javascript
// 监控transition性能
function MonitorPerformance() {
const [isPending, startTransition] = useTransition();
const startTimeRef = useRef(null);
useEffect(() => {
if (isPending) {
startTimeRef.current = performance.now();
} else if (startTimeRef.current) {
const duration = performance.now() - startTimeRef.current;
console.log('Transition duration:', duration);
if (duration > 1000) {
console.warn('Slow transition detected');
}
}
}, [isPending]);
}常见问题
Q1: useTransition和useDeferredValue的区别?
A: useTransition主动标记更新为低优先级;useDeferredValue被动延迟值的更新。
Q2: transition会延迟多久?
A: 不固定,由React调度器智能决定,通常在用户感知不到的情况下尽快完成。
Q3: isPending为true时可以触发新的transition吗?
A: 可以,新transition会取消旧的。
Q4: transition中的错误如何处理?
A: 使用try-catch或ErrorBoundary捕获。
Q5: 所有耗时操作都应该用transition吗?
A: 不是,只有非紧急的UI更新才适合。
Q6: transition和Suspense的关系?
A: 可以配合使用,transition可以避免Suspense闪烁。
Q7: 如何测试使用了transition的组件?
A: 使用waitFor等待transition完成。
Q8: transition会影响SEO吗?
A: 不会,SSR时仍然同步渲染。
Q9: 能否配置transition的优先级?
A: 不能直接配置,但可以通过嵌套控制。
Q10: React 19对useTransition有什么改进?
A: 更稳定的API、更好的性能、更准确的isPending。
总结
核心要点
1. useTransition用途
✅ 标记非紧急更新
✅ 保持UI响应
✅ 提供pending状态
✅ 可中断渲染
2. 最佳实践
✅ 只包裹耗时操作
✅ 配合memo优化
✅ 处理竞态条件
✅ 正确的错误处理
3. 常见场景
✅ 搜索/过滤
✅ Tab切换
✅ 数据可视化
✅ 分页/无限滚动实践建议
1. 判断是否需要transition
2. 优化transition边界
3. 监控性能指标
4. 处理边缘情况
5. 充分测试useTransition是React并发特性的核心,合理使用能显著提升用户体验。