学习React-10-useTransition

发布于:2025-09-12 ⋅ 阅读:(18) ⋅ 点赞:(0)
useTransition

useTransition 是 React 中的一个 Hook,用于标记非紧急的状态更新,允许在并发模式下延迟渲染,避免阻塞高优先级的交互(如用户输入或动画)。它返回一个包含 isPending 标志和 startTransition 函数的数组

基本用法
const [isPending, startTransition] = useTransition();
  • isPending:布尔值,表示是否有过渡中的状态更新未完成。
  • startTransition:函数,用于包裹非紧急的状态更新。
使用场景
  1. 优化渲染性能:将耗时的状态更新(如大型列表筛选)标记为低优先级,避免页面卡顿。
  2. 用户输入响应:确保输入框等高优先级交互的即时响应,延迟其他次要更新。
小栗子

需求: 实现输入框输入内容的模糊检索功能。
前置工具:mockjsAntDesigin

编写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对比效果:

注意事项
  1. 滥用 useTransition
    将所有的状态更新都包裹在 useTransition 中,会导致性能优化失效,甚至可能增加不必要的开销。
 /* ---------- 1. 滥用:把所有同步更新也包起来 ---------- */
  function handleClick1() {
    // ❌ 同步更新根本没必要用 transition,反而多一次调度
    startTransition(() => {
      setPage('/about');
    });
  }
  1. 忽略 isPending 状态
    未使用 isPending 提供加载反馈,导致用户无法感知过渡状态,影响用户体验。
/* ---------- 2. 忽略 isPending:用户看不到加载指示 ---------- */
  function handleClick2() {
    startTransition(() => {
      // 假装拉数据
      fetch('/api/list')
        .then((r) => r.json())
        .then((data) => setList(data));
    });
    // ❌ 这里没用到 isPending,按钮不会转圈,用户以为卡死
  1. 在同步任务中使用
    useTransition 仅适用于异步状态更新(如数据获取),在同步任务中使用无意义。
/* ---------- 3. 在纯同步任务里用 transition ---------- */
  function handleClick3() {
    // ❌ 下面这段代码没有任何异步,包 transition 等于空转
    startTransition(() => {
      const next = list.length + 1;
      setList((l) => [...l, next]);
    });
  }
  1. 未正确处理错误
    过渡期间未捕获潜在错误,可能导致应用崩溃或状态不一致。
  /* ---------- 4. 过渡期间不 catch 错误 ---------- */
 function handleClick4() {
    startTransition(() => {
      // ❌ 一旦接口挂掉,异常会向上冒泡,整棵组件树可能白屏
      fetch('/api/unsafe')
        .then((r) => r.json())
        .then((d) => setList(d));
    });
  }

  1. 嵌套或过度组合
    在复杂组件中嵌套多个 useTransition,可能导致逻辑混乱和性能问题。
 /* ---------- 5. 嵌套 / 过度组合 ---------- */
  function handleClick5() {
    // ❌ 嵌套两层 transition,逻辑难读,调度成本翻倍
    startTransition(() => {
      setPage('/shop');
      startTransition(() => {
        setList([]);
      });
    });
  }

网站公告

今日签到

点亮在社区的每一天
去签到