useTransition
useTransition 是 React 中的一个 Hook,用于标记非紧急的状态更新,允许在并发模式下延迟渲染,避免阻塞高优先级的交互(如用户输入或动画)。它返回一个包含 isPending 标志和 startTransition 函数的数组
基本用法
ts
const [isPending, startTransition] = useTransition();
- isPending:布尔值,表示是否有过渡中的状态更新未完成。
- startTransition:函数,用于包裹非紧急的状态更新。
使用场景
- 优化渲染性能:将耗时的状态更新(如大型列表筛选)标记为低优先级,避免页面卡顿。
- 用户输入响应:确保输入框等高优先级交互的即时响应,延迟其他次要更新。
小栗子
需求: 实现输入框输入内容的模糊检索功能。
前置工具:mockjs、AntDesigin
编写api插件 vite.config.ts
ts
// 导入 Vite 核心配置函数
import { defineConfig } from 'vite'
// 导入 React SWC 插件,提供快速的 React 支持和热更新
import react from '@vitejs/plugin-react-swc'
// 导入 Vite 插件类型定义
import type { Plugin } from 'vite'
// 导入 Mock.js 用于生成模拟数据
import mockjs from 'mockjs'
// 导入 Node.js url 模块用于解析 URL
import url from 'node:url'
/**
* 自定义 Vite Mock 服务插件
* 功能:在开发环境下提供 API 模拟服务
* 用途:前端开发时无需依赖真实后端 API,可以使用模拟数据进行开发和测试
*/
const viteMockService = (): Plugin => {
return {
// 插件名称,用于调试和识别
name: 'vite-mock-service',
/**
* 配置开发服务器
* @param server Vite 开发服务器实例
*/
configureServer(server) {
/**
* 注册 API 路由中间件
* 路径:/api/list
* 功能:返回包含1000条模拟数据的列表
*/
server.middlewares.use('/api/list', (req, res) => {
// 设置响应头为 JSON 格式
res.setHeader('Content-Type', 'application/json')
// 解析请求 URL 中的查询参数
// url.parse(原始地址, 是否格式化查询参数为对象)
const parseUrl = url.parse(req.originalUrl, true).query
/**
* 使用 Mock.js 生成模拟数据
* 数据结构:
* - list: 包含1000个对象的数组
* - 每个对象包含:id(自增)、name(来自查询参数)、address(随机地址)
*/
const data = mockjs.mock({
'list|1000': [ // 生成1000条数据
{
'id|+1': 1, // id 字段,从1开始自增
name: parseUrl.keyWord, // name 字段,使用查询参数中的 keyWord 值
'address': '@county(true)' // address 字段,生成随机的完整县级地址
}
]
})
// 将模拟数据转换为 JSON 字符串并返回给客户端
res.end(JSON.stringify(data))
})
}
}
}
/**
* Vite 配置
* 官方文档:https://vite.dev/config/
*/
export default defineConfig({
/**
* 插件配置
* - react(): 提供 React 支持,使用 SWC 编译器实现快速构建和热更新
* - viteMockService(): 自定义 Mock 服务插件,提供开发环境下的 API 模拟
*/
plugins: [react(), viteMockService()],
})
编写component组件 index.tsx
ts
import React, { useState, useTransition } from 'react'
import { Input, List } from 'antd'
// 定义列表项数据类型
interface ResultType {
id: number // 唯一标识符
name: string // 名称
address: string // 地址
}
/**
* 使用 useTransition 的搜索列表组件
* 功能:根据输入关键词搜索并展示地址包含名称的数据项
* 特性:使用 React 18 的 useTransition 优化用户体验
*/
export function UseTransition() {
// 输入框内容状态
const [val, setVal] = useState('')
// 返回地址列表状态
const [list, setList] = useState<ResultType[]>([])
// 使用 useTransition 处理非紧急状态更新,避免阻塞用户交互
const [isPending, startTransition] = useTransition()
/**
* 处理输入框内容变化
* @param e - 输入框变化事件
*/
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value
// 立即更新输入框内容(紧急更新)
setVal(value)
// 发起 API 请求获取搜索结果
fetch(`api/list/keyWord?keyWord=${value}`).then(res => res.json()).then(res => {
// 使用 startTransition 包装列表更新(非紧急更新)
// 这样可以避免搜索结果更新时阻塞输入框的响应
startTransition(() => {
setList(res.list)
})
// 注释:不使用 startTransition 的传统方式
// setList(res.list)
})
}
return (
<div>
{/* 搜索输入框 */}
<Input value={val} onChange={handleChange} />
{/* 搜索结果列表 */}
<List
// 过滤数据:只显示地址包含名称的项目
dataSource={list.filter(item => item.address.includes(item.name))}
// 显示加载状态,当 transition 正在进行时显示
loading={isPending}
// 渲染每个列表项
renderItem={(item: ResultType) => (
<List.Item>
<List.Item.Meta
title={item.name} // 显示名称作为标题
description={item.address} // 显示地址作为描述
/>
</List.Item>
)}
/>
</div>
)
}
使用useTransition对比效果:
注意事项
- 滥用 useTransition
将所有的状态更新都包裹在 useTransition 中,会导致性能优化失效,甚至可能增加不必要的开销。
ts
/* ---------- 1. 滥用:把所有同步更新也包起来 ---------- */
function handleClick1() {
// ❌ 同步更新根本没必要用 transition,反而多一次调度
startTransition(() => {
setPage('/about');
});
}
- 忽略 isPending 状态
未使用 isPending 提供加载反馈,导致用户无法感知过渡状态,影响用户体验。
ts
/* ---------- 2. 忽略 isPending:用户看不到加载指示 ---------- */
function handleClick2() {
startTransition(() => {
// 假装拉数据
fetch('/api/list')
.then((r) => r.json())
.then((data) => setList(data));
});
// ❌ 这里没用到 isPending,按钮不会转圈,用户以为卡死
- 在同步任务中使用
useTransition 仅适用于异步状态更新(如数据获取),在同步任务中使用无意义。
ts
/* ---------- 3. 在纯同步任务里用 transition ---------- */
function handleClick3() {
// ❌ 下面这段代码没有任何异步,包 transition 等于空转
startTransition(() => {
const next = list.length + 1;
setList((l) => [...l, next]);
});
}
- 未正确处理错误
过渡期间未捕获潜在错误,可能导致应用崩溃或状态不一致。
ts
/* ---------- 4. 过渡期间不 catch 错误 ---------- */
function handleClick4() {
startTransition(() => {
// ❌ 一旦接口挂掉,异常会向上冒泡,整棵组件树可能白屏
fetch('/api/unsafe')
.then((r) => r.json())
.then((d) => setList(d));
});
}
- 嵌套或过度组合
在复杂组件中嵌套多个 useTransition,可能导致逻辑混乱和性能问题。
ts
/* ---------- 5. 嵌套 / 过度组合 ---------- */
function handleClick5() {
// ❌ 嵌套两层 transition,逻辑难读,调度成本翻倍
startTransition(() => {
setPage('/shop');
startTransition(() => {
setList([]);
});
});
}