1.背景
useTransition
是 React 18 引入的一个并发模式下的 Hook,用于区分紧急和非紧急的状态更新,提升应用的响应性和用户体验;- 它可以管理 UI 中的过渡状态,特别是在处理长时间运行的状态更新时。
- 它允许你将某些更新标记为“过渡”状态,这样 React 可以优先处理更重要的更新,比如用户输入,同时延迟处理过渡更新。
- 以下是其核心要点、使用场景和注意事项:
2.核心功能
- 标记过渡状态:将非紧急的状态更新标记为“过渡”(Transition),允许 React 延迟处理这些更新,优先处理高优先级任务(如用户交互)。
- 保持 UI 响应:在状态转换期间,组件仍能保持响应,避免界面卡顿。
- 提供加载状态:通过
isPending
指示过渡任务是否正在进行,可配合加载指示器(如 Spinner)提升用户体验。
3.基本用法
3.1 eg:
import { useTransition } from 'react';
function MyComponent() {
const [isPending, startTransition] = useTransition();
const [count, setCount] = useState(0);
const handleClick = () => {
startTransition(() => {
setCount(c => c + 1); // 标记为过渡更新
});
};
return (
<div>
{isPending && <Spinner />} {/* 显示加载指示器 */}
<button onClick={handleClick}>Increment</button>
<p>Count: {count}</p>
</div>
);
}
3.1 参数
useTransition
不需要任何参数
3.2 返回值
useTransition
返回一个数组 包含两个元素[isPending, startTransition]
isPending
:布尔值,表示过渡任务是否正在进行。startTransition
:函数,用于包裹低优先级的状态更新。
4、使用场景
4.1 大量数据渲染
例如,过滤或排序大型列表时,避免界面卡顿。
function DataGrid() {
const [data, setData] = useState([]);
const [isPending, startTransition] = useTransition();
const [filter, setFilter] = useState('');
const handleFilterChange = (newFilter) => {
setFilter(newFilter);
startTransition(() => {
const filteredData = processLargeDataSet(newFilter);
setData(filteredData);
});
};
return (
<div>
<input value={filter} onChange={e => handleFilterChange(e.target.value)} />
{isPending ? <LoadingGrid /> : <VirtualizedGrid data={data} />}
</div>
);
}
4.2 路由切换:
预加载下一页数据时保持当前页响应。
function App() {
const [isPending, startTransition] = useTransition();
const [currentPage, setCurrentPage] = useState('home');
const navigate = (page) => {
startTransition(() => {
setCurrentPage(page);
});
};
return (
<div>
<Navigation onNavigate={navigate} />
{isPending ? <PageTransitionSpinner /> : <Page name={currentPage} />}
</div>
);
}
4.3 表单验证
实时响应用户输入,同时延迟更新验证结果。
function ComplexForm() {
const [formData, setFormData] = useState({});
const [errors, setErrors] = useState({});
const [isPending, startTransition] = useTransition();
const handleChange = (e) => {
const { name, value } = e.target;
setFormData(prev => ({ ...prev, [name]: value }));
startTransition(() => {
const validationErrors = validateFormField(name, value);
setErrors(prev => ({ ...prev, [name]: validationErrors }));
});
};
return (
<form>
<input name="email" onChange={handleChange} value={formData.email || ''} />
{isPending ? <ValidatingIndicator /> : (errors.email && <ErrorMessage error={errors.email} />)}
</form>
);
}
4.4 搜索或筛选功能
实时响应用户输入,同时延迟更新结果展示。
function SearchResults() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [isPending, startTransition] = useTransition();
const handleSearch = (e) => {
setQuery(e.target.value);
startTransition(() => {
const searchResults = performSearch(e.target.value);
setResults(searchResults);
});
};
return (
<div>
<input value={query} onChange={handleSearch} />
{isPending ? <div>Loading...</div> : (
<ul>
{results.map(result => (
<li key={result.id}>{result.title}</li>
))}
</ul>
)}
</div>
);
}
5. 场景模拟
创建了一个简单的输入框和一个列表,用于展示基于输入关键词的结果。
mockjs文档地址:https://github.com/nuysoft/Mock/wiki/Getting-Started
5.1 安装使用到的插件
pnpm add mockjs antd
pnpm add -D @types/mockjs @type/node
package.json
{
....
"dependencies": {
"antd": "^5.24.9",
"mockjs": "^1.1.0",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"devDependencies": {
"@types/mockjs": "^1.0.10",
"@types/node": "^22.15.3",
"@types/react": "^19.1.2",
.....
}
}
5.2 编写 vite.config.ts
结合 vite插件实现一个api, 这个api可以帮助我们模拟数据。
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import type { Plugin } from 'vite'
import mockjs from 'mockjs'
// 使用 URL 模块解析 URL
import url from 'node:url'
/**
* 创建一个 Vite Mock 服务器插件
* @returns Vite 插件实例
*/
const viteMockServer = (): Plugin => {
return {
// 插件名称
name: 'vite-plugin-mock',
// 配置开发服务器
configureServer(server) {
// 添加中间件处理 mock 请求
server.middlewares.use('/api/mock/list', (req, res) => {
// 解析请求 URL 中的查询参数
const parseurl = url.parse(req.originalUrl ?? '', true).query;
// 设置响应头为 JSON 格式
res.setHeader('Content-Type', 'application/json');
// 使用 mockjs 生成模拟数据
const data = mockjs.mock({
'list|2000': [
{
id: '@guid',
// name: '@cword(2, 4)',
name: parseurl.key,
age: '@integer(18, 30)',
address: '@county(true)',
},
],
});
// 将数据转换为 JSON 字符串并发送响应
res.end(JSON.stringify(data));
})
}
}
}
// https://vite.dev/config/
export default defineConfig({
plugins: [react(), viteMockServer()],
})
编写完成访问我们的接口
http://localhost:5174/api/list?keyWord=xx
5174为默认端口,可以自行更改,返回数据如下{ "list": [ { "id": "DCe1D11e-8D24-31e6-fE76-5AbAA6cf7E6F", "name": "a", "age": 25, "address": "西藏自治区 日喀则地区 仲巴县" }, { "id": "EAb5ffb6-b43B-93cC-cDfb-18A7Ce15BFda", "name": "a", "age": 21, "address": "安徽省 马鞍山市 花山区" } ....... ] }
5.3 业务组件编写
App.tsx
- 输入框和状态管理 使用 useState Hook 管理输入框的值和结果列表。 每次输入框的内容变化时,handleInputChange 函数会被触发,它会获取用户输入的值,并进行 API 请求。
- API 请求 在 handleInputChange 中,输入的值会作为查询参数发送到 /api/list API。API 返回的数据用于更新结果列表。 为了优化用户体验,我们将结果更新放在 startTransition 函数中,这样 React 可以在处理更新时保持输入框的响应性。
- 使用 useTransition useTransition 返回一个布尔值 isPending,指示过渡任务是否仍在进行中。 当用户输入时,如果正在加载数据,我们会显示一个简单的“loading…”提示,以告知用户当前操作仍在进行。
- 列表渲染 使用 List 组件展示返回的结果,列表项显示每个结果的 name 和 address。
// 使用 React 和 Ant Design 组件实现一个模拟带搜索功能的列表
import { Input, List, Spin } from 'antd'
import { useState, useTransition, useCallback } from 'react'
interface ListItem {
id: string;
name: string;
address: string;
age: number;
}
function App() {
const [list, setList] = useState<ListItem[]>([]);
const [inputValue, setInputValue] = useState('');
const [isPending, startTransition] = useTransition();
// 处理输入框变化,使用 useCallback 优化性能
const handleInputChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
setInputValue(value);
// 发起请求获取数据
fetch(`/api/mock/list?key=${value}`)
.then((res) => res.json())
.then((res) => {
// 使用 startTransition 包裹状态更新,优化大数据渲染性能
startTransition(() => {
setList(res.list)
});
})
.catch(err => {
console.error('获取数据失败:', err);
});
}, []);
return (
<>
<Input
placeholder="请输入搜索关键词"
value={inputValue}
onChange={handleInputChange}
/>
{isPending ? (
<Spin tip="加载中..." />
) : (
<List
dataSource={list}
renderItem={(item) => (
<List.Item>{item.address}</List.Item>
)}
/>
)}
</>
);
}
export default App;
5.4 测试使用
为了更好的测试结果可以在性能中降级 cpu 渲染速度
6. 注意事项
startTransition必须是同步的
错误做法
startTransition(() => {
// ❌ 在调用 startTransition 后更新状态
setTimeout(() => {
setPage('/about');
}, 1000);
});
正确做法
setTimeout(() => {
startTransition(() => {
// ✅ 在调用 startTransition 中更新状态
setPage('/about');
});
}, 1000);
async await 错误做法
startTransition(async () => {
await someAsyncFunction();
// ❌ 在调用 startTransition 后更新状态
setPage('/about');
});
正确做法
await someAsyncFunction();
startTransition(() => {
// ✅ 在调用 startTransition 中更新状态
setPage('/about');
});
7. 原理剖析
useTransition 的核心原理是将一部分状态更新处理为低优先级任务,这样可以将关键的高优先级任务先执行,而低优先级的过渡更新则会稍微延迟处理。这在渲染大量数据、进行复杂运算或处理长时间任务时特别有效。React 通过调度机制来管理优先级:
- 高优先级更新:直接影响用户体验的任务,比如表单输入、按钮点击等。
- 低优先级更新:相对不影响交互的过渡性任务,比如大量数据渲染、动画等,这些任务可以延迟执行。
+-----------------------+
| App |
| |
| +--------------+ |
| | Input | |
| +--------------+ |
| |
| +--------------+ |
| | Display | |
| +--------------+ |
+-----------------------+
用户输入
|
v
[高优先级更新] ---> [调度器] ---> [React 更新组件]
|
+---> [低优先级过渡更新] --> [调度器] --> [等待处理]
8.扩展
useTransition
与防抖的区别?
seTransition
与 防抖在前端开发中都是用于优化性能的手段,但它们的核心原理、应用场景和实现方式存在显著区别,以下是详细对比:
核心原理对比
useTransition
- 基于React的并发模式(Concurrent Features),通过将状态更新标记为“过渡”(Transition)实现。
- 允许React在后台处理低优先级任务,优先响应高优先级操作(如用户输入),从而保持界面流畅。
- 更新过程可中断,避免长时间阻塞主线程,提升用户体验。
- 防抖(Debounce)
- 延迟函数执行,直到事件触发后的一段时间内没有再次触发。
- 适用于需要等待用户停止操作后再执行的场景(如搜索输入、表单验证)。
应用场景对比
useTransition
适用场景- 大数据列表过滤:当用户输入搜索内容时,通过
useTransition
将筛选任务标记为低优先级,避免阻塞输入框的实时更新,保持输入流畅性。 - 复杂UI更新:在渲染需要消耗大量时间的页面时,使用
useTransition
优化视图切换体验。 - 表单提交与筛选器切换:需要延迟渲染的操作(如异步数据加载)中,
useTransition
可确保用户交互的即时响应。
- 大数据列表过滤:当用户输入搜索内容时,通过
- 防抖适用场景
- 搜索框输入检测:等待用户停止输入后再执行搜索,减少无效请求。
- 手机号和邮箱验证:在用户停止输入后验证格式,避免实时验证的性能开销。
- 窗口大小变化后的重新渲染:在窗口调整结束后执行布局计算,避免频繁重绘。
优缺点对比
useTransition
优点- 更新协调过程可中断,避免长时间阻塞主线程。
- 用户操作可及时得到响应,提升交互体验。
- 不需要开发者手动配置时间间隔,React自动优化。
useTransition
缺点- 仅适用于React 18及以上版本,且需配合并发模式使用。
- 对于简单场景,可能引入不必要的复杂性。
- 防抖优点
- 可确保在用户停止操作后执行函数,减少无效请求。
- 实现简单,适用于等待用户输入的场景。
- 防抖缺点
- 可能导致用户输入长时间得不到响应。
- 无法处理需要即时反馈的操作。