学习React-10-useTransition
useTransition
useTransition 是 React 中的一个 Hook,用于标记非紧急的状态更新,允许在并发模式下延迟渲染,避免阻塞高优先级的交互(如用户输入或动画)。它返回一个包含 isPending 标志和 startTransition 函数的数组
基本用法
const [isPending, startTransition] = useTransition();
- isPending:布尔值,表示是否有过渡中的状态更新未完成。
- startTransition:函数,用于包裹非紧急的状态更新。
使用场景
- 优化渲染性能:将耗时的状态更新(如大型列表筛选)标记为低优先级,避免页面卡顿。
- 用户输入响应:确保输入框等高优先级交互的即时响应,延迟其他次要更新。
小栗子
需求: 实现输入框输入内容的模糊检索功能。
前置工具:mockjs、AntDesigin
编写api插件 vite.config.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
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 中,会导致性能优化失效,甚至可能增加不必要的开销。
/* ---------- 1. 滥用:把所有同步更新也包起来 ---------- */function handleClick1() {// ❌ 同步更新根本没必要用 transition,反而多一次调度startTransition(() => {setPage('/about');});}
- 忽略 isPending 状态
未使用 isPending 提供加载反馈,导致用户无法感知过渡状态,影响用户体验。
/* ---------- 2. 忽略 isPending:用户看不到加载指示 ---------- */function handleClick2() {startTransition(() => {// 假装拉数据fetch('/api/list').then((r) => r.json()).then((data) => setList(data));});// ❌ 这里没用到 isPending,按钮不会转圈,用户以为卡死
- 在同步任务中使用
useTransition 仅适用于异步状态更新(如数据获取),在同步任务中使用无意义。
/* ---------- 3. 在纯同步任务里用 transition ---------- */function handleClick3() {// ❌ 下面这段代码没有任何异步,包 transition 等于空转startTransition(() => {const next = list.length + 1;setList((l) => [...l, next]);});}
- 未正确处理错误
过渡期间未捕获潜在错误,可能导致应用崩溃或状态不一致。
/* ---------- 4. 过渡期间不 catch 错误 ---------- */function handleClick4() {startTransition(() => {// ❌ 一旦接口挂掉,异常会向上冒泡,整棵组件树可能白屏fetch('/api/unsafe').then((r) => r.json()).then((d) => setList(d));});}
- 嵌套或过度组合
在复杂组件中嵌套多个 useTransition,可能导致逻辑混乱和性能问题。
/* ---------- 5. 嵌套 / 过度组合 ---------- */function handleClick5() {// ❌ 嵌套两层 transition,逻辑难读,调度成本翻倍startTransition(() => {setPage('/shop');startTransition(() => {setList([]);});});}