Skip to content

useTransition

帮你对状态更新进行“限流与降级”,从而防止耗时的任务阻塞用户的高频操作(如打字、点击、滚动),让应用保持丝滑流畅。

背景

在默认情况下,React 的所有状态更新都被视为紧急任务(Urgent Updates)。 想象一个经典的“搜索框”场景:用户输入关键字,页面不仅要更新输入框里的文字,还要同步过滤一个包含几万条数据的列表。

js
// 默认做法:两个更新一样紧急
const handleChange = (e) => {
  setInputValue(e.target.value) // 1. 更新输入框(必须快!)
  setSearchQuery(e.target.value) // 2. 过滤大列表(非常慢!)
}

因为过滤大列表非常耗时,它会霸占浏览器的 JS 线程。当用户飞快地打字时,输入框会明显卡顿、甚至卡死,因为 React 必须等上一次大列表渲染完,才能处理下一次的打字输入。

基本语法

js
const [isPending, startTransition] = useTransition()
  • isPending:一个布尔值。当过渡任务正在后台偷偷渲染、还没渲染完成时,它是 true;渲染完成后变成 false。你可以用它来展示一个微小的加载菊花图(Spinner)。

  • startTransition:一个函数。你把不紧急、耗时的状态更新代码包裹在它的回调函数里。

用法

先利用mockjs模拟数据

sh
npm i mockhs

结合vite插件

vite.config.ts
ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
import type { Plugin } from 'vite'
import mockjs from 'mockjs'
import url from 'node:url'
const viteMockServer = (): Plugin => {
  return {
    name: 'vite-mock-server',
    //使用vite插件的钩子函数
    configureServer(server) {
      server.middlewares.use('/api/list', async (req, res) => {
        const parsedUrl = url.parse(req.originalUrl, true)
        //获取url参数 true表示返回对象 {keyWord: 'xx'}
        const query = parsedUrl.query
        res.setHeader('Content-Type', 'application/json')
        const data = mockjs.mock({
          //返回1000条数据
          'list|1000': [
            {
              'id|+1': 1, //id自增
              name: query.keyWord, //name为url参数中的keyWord
              address: '@county(true)' //address为随机地址
            }
          ]
        })
        //返回数据
        res.end(JSON.stringify(data))
      })
    }
  }
}
// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react(), viteMockServer()]
})

编写完成后访问我们的接口http://localhost:5174/api/list?keyWord=xx即可访问数据

前端界面利用antd

tsx
import { useTransition, useState } from 'react';
import { Input, Flex, List } from "antd";
interface Item {
   id: number;
   name: string;
   address: string
}
const App = () => {
   const [inputValue, setInputValue] = useState('');
   const [isPending, startTransition] = useTransition(); // 开始过渡
   const [list, setList] = useState<Item[]>([])
   const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
      const value = e.target.value
      setInputValue(value)
      fetch(`/api/list?keyWord=${value}`).then(res => res.json()).then(data => {
         const res = data?.list ?? [];
         // 使用过渡 useTransition
         startTransition(() => {
            setList([...res])
         })
         //不使用过渡 useTransition
         //setList([...res])
      })
   }
   return (
      <>
         <Flex>
            <Input
               value={inputValue}
               onChange={handleInputChange} // 实时更新
               placeholder="请输入姓名"
            />
         </Flex>
         {
            isPending && <div>loading...</div>
         }
         <List
            dataSource={list}
            renderItem={(item) => (
               <List.Item>
                  <List.Item.Meta
                     title={item.name}
                     description={item.address}
                  />
               </List.Item>
            )}
         />
      </>
   );
}

export default App;

注意事项

传给startTransition的回调函数必须是同步的,不能在里面写setTimeoutfetch异步请求