React 中hooks之useTransition使用总结

目录

  1. 概述
  2. 基本用法
  3. 使用场景
  4. 最佳实践
  5. 注意事项

概述

什么是 useTransition?

useTransition 是 React 18 引入的新 Hook,用于标记非紧急的状态更新。它允许组件在状态转换期间保持响应,通过将某些更新标记为"过渡"来推迟它们的渲染。

主要特点

  • 保持 UI 响应性
  • 区分紧急和非紧急更新
  • 提供加载状态指示器
  • 不会阻塞用户交互

基本用法

1. 基本语法

typescript 复制代码
import { useTransition } from 'react';

function MyComponent() {
  const [isPending, startTransition] = useTransition();
  const [count, setCount] = useState(0);

  const handleClick = () => {
    startTransition(() => {
      setCount(c => c + 1);  // 这个更新被标记为过渡,当某次更新造成页面阻塞时,用户点击其他组件操作,此时会降低此次更新的优先级,不阻塞页面渲染先更新优先级高的操作,startTransition中只能写同步的代码,异步代码会打断低优先级,比如不能使用setTimeout
    });
  };

  return (
    <div>
      {isPending && <Spinner />}
      <button onClick={handleClick}>Increment</button>
      <p>Count: {count}</p>
    </div>
  );
}//isPending为true是表示当前渲染阻塞避免页面出现卡顿现象先显示loading状态组件,直到isPending为false时将展示渲染好的组件

2. 带加载状态的示例

typescript 复制代码
function SearchResults() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  const [isPending, startTransition] = useTransition();

  const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
    // 立即更新输入值(紧急更新)
    setQuery(e.target.value);

    // 将搜索结果更新标记为过渡(非紧急更新)
    startTransition(() => {
      // 模拟搜索操作
      const searchResults = performSearch(e.target.value);
      setResults(searchResults);
    });
  };

  return (
    <div>
      <input value={query} onChange={handleSearch} />
      {isPending ? (
        <div>Loading...</div>
      ) : (
        <ul>
          {results.map(result => (
            <li key={result.id}>{result.title}</li>
          ))}
        </ul>
      )}
    </div>
  );
}

使用场景

1. 大量数据渲染

typescript 复制代码
function DataGrid() {
  const [data, setData] = useState([]);
  const [isPending, startTransition] = useTransition();
  const [filter, setFilter] = useState('');

  const handleFilterChange = (newFilter: string) => {
    setFilter(newFilter);  // 立即更新过滤条件

    startTransition(() => {
      // 延迟大量数据的过滤和渲染
      const filteredData = processLargeDataSet(newFilter);
      setData(filteredData);
    });
  };

  return (
    <div>
      <input 
        value={filter} 
        onChange={e => handleFilterChange(e.target.value)} 
      />
      {isPending ? (
        <LoadingGrid />
      ) : (
        <VirtualizedGrid data={data} />
      )}
    </div>
  );
}

2. 路由切换

typescript 复制代码
function App() {
  const [isPending, startTransition] = useTransition();
  const [currentPage, setCurrentPage] = useState('home');

  const navigate = (page: string) => {
    // 立即更新导航状态
    startTransition(() => {
      setCurrentPage(page);
    });
  };

  return (
    <div>
      <Navigation onNavigate={navigate} />
      {isPending ? (
        <PageTransitionSpinner />
      ) : (
        <Page name={currentPage} />
      )}
    </div>
  );
}

3. 表单验证

typescript 复制代码
function ComplexForm() {
  const [formData, setFormData] = useState({});
  const [errors, setErrors] = useState({});
  const [isPending, startTransition] = useTransition();

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const { name, value } = e.target;
    
    // 立即更新表单值
    setFormData(prev => ({
      ...prev,
      [name]: value
    }));

    // 将复杂的验证逻辑标记为过渡
    startTransition(() => {
      const validationErrors = validateFormField(name, value);
      setErrors(prev => ({
        ...prev,
        [name]: validationErrors
      }));
    });
  };

  return (
    <form>
      <input 
        name="email" 
        onChange={handleChange} 
        value={formData.email || ''} 
      />
      {isPending ? (
        <ValidatingIndicator />
      ) : (
        errors.email && <ErrorMessage error={errors.email} />
      )}
    </form>
  );
}

最佳实践

  1. 区分更新优先级
typescript 复制代码
function UserProfile() {
  const [isPending, startTransition] = useTransition();
  
  const handleInput = (e: React.ChangeEvent<HTMLInputElement>) => {
    // 高优先级:直接更新输入值
    setInputValue(e.target.value);
    
    // 低优先级:更新预览
    startTransition(() => {
      setPreviewData(generatePreview(e.target.value));
    });
  };
}
  1. 合理使用 isPending
typescript 复制代码
function LoadingStates() {
  const [isPending, startTransition] = useTransition();
  
  return (
    <div>
      {/* 使用骨架屏而不是简单的加载指示器 */}
      {isPending ? (
        <Skeleton />
      ) : (
        <Content />
      )}
    </div>
  );
}

注意事项

  1. 不要在 transition 中包含紧急更新
typescript 复制代码
// ❌ 错误示例
startTransition(() => {
  setInputValue(e.target.value);  // 这应该是紧急更新
});

// ✅ 正确示例
setInputValue(e.target.value);    // 紧急更新
startTransition(() => {
  setSearchResults(search(e.target.value));  // 非紧急更新
});
  1. 避免不必要的 transition
typescript 复制代码
// ❌ 不需要 transition
startTransition(() => {
  setCount(count + 1);  // 简单的状态更新不需要 transition
});

// ✅ 适合使用 transition
startTransition(() => {
  setFilteredItems(items.filter(complexFilter));  // 复杂计算
});

总结

  1. useTransition 适用场景:

    • 大量数据处理
    • 复杂 UI 更新
    • 后台计算
    • 非阻塞渲染
  2. 主要优势:

    • 提升用户体验
    • 保持 UI 响应性
    • 优化渲染性能
    • 提供加载状态
  3. 使用建议:

    • 合理区分更新优先级
    • 适当处理加载状态
    • 避免过度使用
    • 配合其他性能优化手段
相关推荐
腾讯TNTWeb前端团队5 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰8 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪9 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪9 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy9 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom10 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom10 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom10 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom10 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom10 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试