Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

hook:useTransition

  • React 18 新增的并发特性 Hook,用于标记低优先级的状态更新
  • 核心作用: 让 UI 保持响应,避免因大量计算或渲染而卡顿
  • 原理: 将状态更新标记为"transition",React 可以中断它来优先处理用户交互

基本语法

const [isPending, startTransition] = useTransition();

// isPending: 布尔值,表示 transition 是否正在进行中
// startTransition: 函数,用于包裹低优先级的状态更新

场景代码示例

  • 【场景 1】大列表渲染优化(最常用):

    • 输入框输入时,列表渲染不阻塞输入

      ❌ 不使用 useTransition - 输入卡顿

      import { useState } from "react";
      
      function SearchList() {
        const [input, setInput] = useState("");
        const [list, setList] = useState([]);
      
        const handleChange = (e) => {
          const value = e.target.value;
          setInput(value); // 高优先级:立即更新输入框
      
          // 低优先级:生成大列表(耗时操作)
          const newList = [];
          for (let i = 0; i < 20000; i++) {
            newList.push(value + " - " + i);
          }
          setList(newList); // ❌ 阻塞输入,造成卡顿
        };
      
        return (
          <div>
            <input value={input} onChange={handleChange} />
            {/* 输入时会卡顿,因为需要等待列表渲染完成 */}
            <ul>
              {list.map((item, i) => (
                <li key={i}>{item}</li>
              ))}
            </ul>
          </div>
        );
      }
      

      ✅ 使用 useTransition - 输入流畅

      import { useState, useTransition } from "react";
      
      function SearchList() {
        const [input, setInput] = useState("");
        const [list, setList] = useState([]);
        const [isPending, startTransition] = useTransition();
      
        const handleChange = (e) => {
          const value = e.target.value;
          setInput(value); // 高优先级:立即更新输入框
      
          // 低优先级:用 startTransition 包裹
          startTransition(() => {
            const newList = [];
            for (let i = 0; i < 20000; i++) {
              newList.push(value + " - " + i);
            }
            setList(newList); // ✅ 不阻塞输入,保持响应
          });
        };
      
        return (
          <div>
            <input value={input} onChange={handleChange} />
            {isPending && <div>加载中...</div>} {/* 显示加载状态 */}
            <ul style={{ opacity: isPending ? 0.5 : 1 }}>
              {" "}
              {/* 降低透明度 */}
              {list.map((item, i) => (
                <li key={i}>{item}</li>
              ))}
            </ul>
          </div>
        );
      }
      
    • 效果: 输入框立即响应,列表延迟更新,用户体验更好

  • 【场景 2】选项卡切换(常用):

    • 切换 Tab 时,保持界面响应

      import { useState, useTransition } from "react";
      
      function Tabs() {
        const [tab, setTab] = useState("home");
        const [isPending, startTransition] = useTransition();
      
        const handleTabClick = (newTab) => {
          startTransition(() => {
            setTab(newTab); // 标记为低优先级
          });
        };
      
        return (
          <div>
            <button
              onClick={() => handleTabClick("home")}
              disabled={isPending && tab !== "home"}
            >
              首页
            </button>
            <button
              onClick={() => handleTabClick("posts")}
              disabled={isPending && tab !== "posts"}
            >
              文章 {isPending && tab === "posts" && "(加载中...)"}
            </button>
            <button
              onClick={() => handleTabClick("contact")}
              disabled={isPending && tab !== "contact"}
            >
              联系
            </button>
      
            <div style={{ opacity: isPending ? 0.5 : 1 }}>
              {tab === "home" && <HomePage />}
              {tab === "posts" && <PostsPage />} {/* 假设这是重量级组件 */}
              {tab === "contact" && <ContactPage />}
            </div>
          </div>
        );
      }
      
      // 假设这是一个渲染缓慢的组件
      function PostsPage() {
        const posts = Array.from({ length: 5000 }, (_, i) => `Post ${i}`);
        return (
          <ul>
            {posts.map((post) => (
              <li key={post}>{post}</li>
            ))}
          </ul>
        );
      }
      
    • 效果: 点击按钮立即响应,内容延迟加载

  • 【场景 3】搜索结果过滤(常用):

    • 实时搜索时,保持输入流畅

      import { useState, useTransition } from "react";
      
      function SearchProducts() {
        const [query, setQuery] = useState("");
        const [filteredProducts, setFilteredProducts] = useState(products);
        const [isPending, startTransition] = useTransition();
      
        const handleSearch = (e) => {
          const value = e.target.value;
          setQuery(value); // 立即更新输入框
      
          startTransition(() => {
            // 复杂的过滤逻辑
            const filtered = products.filter((product) =>
              product.name.toLowerCase().includes(value.toLowerCase()),
            );
            setFilteredProducts(filtered);
          });
        };
      
        return (
          <div>
            <input
              type="text"
              value={query}
              onChange={handleSearch}
              placeholder="搜索商品..."
            />
            {isPending && <span>搜索中...</span>}
      
            <ul>
              {filteredProducts.map((product) => (
                <li key={product.id}>{product.name}</li>
              ))}
            </ul>
          </div>
        );
      }
      
      const products = Array.from({ length: 10000 }, (_, i) => ({
        id: i,
        name: `商品 ${i}`,
      }));
      
  • 【场景 4】配合 Suspense 使用(进阶):

    • 与 Suspense 结合,实现更优雅的加载状态

      import { useState, useTransition, Suspense } from "react";
      
      function App() {
        const [tab, setTab] = useState("home");
        const [isPending, startTransition] = useTransition();
      
        const handleTabClick = (newTab) => {
          startTransition(() => {
            setTab(newTab);
          });
        };
      
        return (
          <div>
            <button onClick={() => handleTabClick("home")}>首页</button>
            <button onClick={() => handleTabClick("profile")}>
              个人资料 {isPending && "(加载中...)"}
            </button>
      
            <Suspense fallback={<div>Loading...</div>}>
              {tab === "home" && <Home />}
              {tab === "profile" && <Profile />}
            </Suspense>
          </div>
        );
      }
      

useTransition vs setTimeout/debounce

方案优先级控制自动取消状态提示React 优化使用复杂度
useTransition✅ 内置✅ 自动✅ isPending✅ 原生支持
setTimeout❌ 手动❌ 需自己实现
debounce✅ 部分支持❌ 需自己实现

选择原则:

  • 需要保持 UI 响应 + 延迟更新 → 用 useTransition
  • 减少 API 调用次数 → 用 debounce
  • 简单延迟执行 → 用 setTimeout

什么时候使用 useTransition

场景是否适用原因
大列表渲染避免阻塞输入
复杂计算保持界面响应
选项卡切换切换体验更流畅
搜索/过滤输入不卡顿
路由切换页面切换更顺畅
简单的状态更新没必要,过度优化
网络请求⚠️配合 Suspense 使用更好
第三方库状态更新useTransition 只作用于 React 状态

注意事项

  • ⚠️ 只能包裹状态更新

    // ❌ 错误:不能包裹异步操作
    startTransition(async () => {
      const data = await fetchData();
      setData(data);
    });
    
    // ✅ 正确:只包裹状态更新
    const data = await fetchData();
    startTransition(() => {
      setData(data);
    });
    
  • ⚠️ 不要滥用

    // ❌ 不需要:简单的状态更新
    startTransition(() => {
      setCount(count + 1); // 简单更新,没必要用 transition
    });
    
    // ✅ 需要:复杂渲染
    startTransition(() => {
      setList(generateHugeList()); // 复杂操作,适合用 transition
    });
    
  • ⚠️ isPending 在 transition 期间为 true

    const [isPending, startTransition] = useTransition();
    
    startTransition(() => {
      setData(newData);
    });
    
    // isPending 立即变为 true,直到 newData 渲染完成
    console.log(isPending); // true (在 transition 期间)
    
  • ⚠️ transition 可以被中断

    // React 可能会中断 transition 来处理更高优先级的更新
    startTransition(() => {
      setHugeList(data); // 这个更新可能被中断
    });
    
    // 用户点击按钮(高优先级)
    onClick={() => setCount(count + 1)} // 会中断上面的 transition
    
  • 适合 CPU 密集型操作

    // ✅ 适合:大量计算或渲染
    startTransition(() => {
      setFilteredList(list.filter(/* 复杂过滤 */));
    });
    
    // ❌ 不适合:IO 密集型(网络请求)
    // 网络请求应该用其他方式优化
    
  • 可以嵌套多个状态更新

    startTransition(() => {
      setList(newList);
      setPage(newPage);
      setFilter(newFilter);
      // 所有这些更新都是低优先级的
    });
    
  • 配合 isPending 提供更好的用户反馈

    return (
      <div>
        <input onChange={handleChange} />
        {isPending ? <div>搜索中...</div> : <div>{results.length} 条结果</div>}
        <div style={{ opacity: isPending ? 0.5 : 1 }}>
          {/* 降低透明度,而不是完全隐藏 */}
          {results.map((item) => (
            <Item key={item.id} {...item} />
          ))}
        </div>
      </div>
    );
    

实用技巧

  • 技巧 1:自定义 Hook 封装

    function useTransitionState(initialState) {
      const [state, setState] = useState(initialState);
      const [isPending, startTransition] = useTransition();
    
      const setTransitionState = (value) => {
        startTransition(() => {
          setState(value);
        });
      };
    
      return [state, setTransitionState, isPending];
    }
    
    // 使用
    function App() {
      const [list, setList, isPending] = useTransitionState([]);
    
      const handleUpdate = () => {
        setList(generateHugeList()); // 自动包裹在 transition 中
      };
    
      return (
        <div>
          <button onClick={handleUpdate}>更新列表</button>
          {isPending && <div>加载中...</div>}
          <List data={list} />
        </div>
      );
    }
    
  • 技巧 2:与 Loading 状态结合

    function ProductList() {
      const [category, setCategory] = useState("all");
      const [isPending, startTransition] = useTransition();
      const [isLoading, setIsLoading] = useState(false);
    
      const handleCategoryChange = async (newCategory) => {
        setIsLoading(true);
    
        // 网络请求
        const data = await fetchProducts(newCategory);
    
        // 状态更新用 transition
        startTransition(() => {
          setCategory(newCategory);
          setProducts(data);
          setIsLoading(false);
        });
      };
    
      // 显示 Loading:isLoading (网络) 或 isPending (渲染)
      const showLoading = isLoading || isPending;
    
      return (
        <div>
          <select onChange={(e) => handleCategoryChange(e.target.value)}>
            <option value="all">全部</option>
            <option value="electronics">电子产品</option>
          </select>
          {showLoading && <div>加载中...</div>}
          <div style={{ opacity: isPending ? 0.5 : 1 }}>{/* 产品列表 */}</div>
        </div>
      );
    }
    

浏览器兼容性

  • React 18+ 支持
  • 需要现代浏览器支持(ES2015+)
  • 不支持 IE 11

记住:useTransition 让 React 可以中断低优先级的更新,优先处理用户交互,从而保持 UI 的响应性。