React 中 useDeferredValue 和 startTransition 的核心区别与使用场景

React 中 useDeferredValuestartTransition,两者都用于优化性能,但适用场景和实现方式不同。


核心概念解析

1. useDeferredValue:延迟值更新

  • 作用 :告诉 React 延迟更新某个值,直到所有高优先级渲染任务完成。

  • 适用场景:当某个值的更新会触发计算密集型渲染(如复杂列表、图表),但需要优先保证其他高优先级 UI(如输入框)的响应。

  • 机制

    • 保持旧值显示,后台计算新值。
    • 计算完成后,用新值更新 UI,期间允许用户继续交互。
  • 示例

    假设有一个输入框和一个渲染大量数据的列表,每次输入都会触发列表过滤。直接用 query 过滤时,输入会卡顿;用 useDeferredValue 后,输入体验会明显提升。

jsx 复制代码
import React, { useState, useDeferredValue, useMemo } from 'react';

// 假设有一个很大的用户列表
const largeList = Array.from({ length: 10000 }, (_, i) => ({
  id: i,
  name: `User ${i}`,
}));

function ExpensiveList({ filter }) {
  // 这里用 useMemo 模拟一次很慢的过滤操作
  const filteredList = useMemo(() => {
    // 模拟耗时操作
    const start = performance.now();
    while (performance.now() - start < 10) {} // 人为阻塞10ms
    return largeList.filter(user =>
      user.name.toLowerCase().includes(filter.toLowerCase())
    );
  }, [filter]);

  return (
    <ul>
      {filteredList.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

export default function App() {
  const [query, setQuery] = useState('');
  // 获取延迟后的 query
  const deferredQuery = useDeferredValue(query);

  return (
    <div>
      <input
        value={query}
        onChange={e => setQuery(e.target.value)}
        placeholder="搜索用户"
      />
      {/* 这里用延迟值,保证输入框流畅 */}
      <ExpensiveList filter={deferredQuery} />
      {/* 你也可以对比直接用 query,体验会卡顿 */}
      {/* <ExpensiveList filter={query} /> */}
    </div>
  );
}

说明

  • query:输入框内容,实时更新,优先保证输入体验。
  • deferredQuery:用 useDeferredValue 得到的延迟值,只有在主线程空闲时才会更新,传递给耗时组件。
  • ExpensiveList:模拟一个渲染和过滤都很慢的组件。
  • 这样写的好处是:输入框始终流畅,列表渲染不会阻塞用户输入,即使数据量很大也能保证良好体验。

2. startTransition:显式标记过渡更新

  • 作用显式告知 React 某个状态更新可能触发高开销渲染,应作为低优先级任务处理。

  • 适用场景:开发者能直接控制状态更新逻辑时(如搜索框输入后过滤列表)。

  • 机制

    • 包裹状态更新代码,标记为"过渡任务"。
    • React 优先处理高优先级事件(如用户输入),过渡任务可被中断或延迟。
  • 示例

    假设有一个用户列表,根据输入内容实时过滤,过滤操作假设很耗时。

jsx 复制代码
import React, { useState, useTransition } from 'react';

// 假设这是一个渲染大量数据的组件
function ExpensiveList({ users }) {
  // 这里可以模拟耗时渲染,比如用 Array.map 渲染很多项
  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

export default function App({ allUsers }) {
  const [query, setQuery] = useState('');
  const [filteredUsers, setFilteredUsers] = useState(allUsers);
  const [isPending, startTransition] = useTransition();

  const handleChange = (e) => {
    const value = e.target.value;
    setQuery(value); // 立即更新输入框内容(高优先级)

    // 过滤操作作为"过渡"处理(低优先级,可能很慢)
    startTransition(() => {
      const filtered = allUsers.filter(user =>
        user.name.toLowerCase().includes(value.toLowerCase())
      );
      setFilteredUsers(filtered);
    });
  };

  return (
    <div>
      <input value={query} onChange={handleChange} placeholder="搜索用户" />
      {isPending && <div>加载中...</div>}
      <ExpensiveList users={filteredUsers} />
    </div>
  );
}

说明

  • query:输入框的内容,属于高优先级,用户每输入一次立即更新。
  • filteredUsers:过滤后的用户列表,属于低优先级,用 startTransition 包裹,React 会在主线程空闲时再去处理它。
  • isPending:表示过滤操作是否还在进行,可以用来显示加载提示。
  • ExpensiveList:渲染过滤后的用户列表,假设渲染很多数据,比较耗时。

对比

特性 useDeferredValue startTransition
控制对象 值(如 state 或 prop) 状态更新操作(如 setState 调用)
适用场景 无法直接控制状态更新(如值来自父组件) 能直接控制状态更新逻辑(如事件处理函数)
优先级控制 自动延迟低优先级值的更新 显式标记特定更新为低优先级
实现方式 通过缓存旧值,后台计算新值 包裹更新代码,调度为可中断任务
与 UI 的关系 延迟更新 UI 中的某个部分 延迟更新由状态变化触发的 UI
开发者控制权 被动:由 React 自动调度 主动:开发者显式标记过渡任务

底层原理

useDeferredValue

  • 实现 :内部结合了 useStateuseEffect,并利用 React 的并发调度(Transition)机制。

  • 流程

    1. 组件每次渲染时,useDeferredValue 先返回上一次的"延迟值"。
    2. 当依赖的原始值变化时,useEffect 触发,在一个过渡任务(transition)中异步更新延迟值。
    3. 如果在延迟值尚未更新期间原始值再次变化,之前的过渡任务会被中断,重新发起新的过渡任务,确保只渲染最新的值。
    4. 这样可以保证高优先级的交互(如输入)不会被低优先级的重渲染阻塞,提升用户体验。

startTransition

  • 实现startTransition 会临时切换 React 内部的优先级配置,仅将包裹在回调函数中的状态更新标记为"过渡任务"(低优先级),并发模式下这些任务可以被中断或延后执行。

  • 流程

    1. 调用 startTransition 时,React 内部会设置一个"过渡任务"标志,仅对本次回调函数内的状态更新生效。
    2. 执行回调函数,回调中的所有状态更新(如 setState)会被标记为低优先级。
    3. 回调执行完毕后,React 会恢复原有的优先级配置,后续的状态更新不再受影响。
    4. 这样,只有你明确包裹在 startTransition 回调里的状态更新是低优先级,其他更新(如用户输入、点击等)依然是高优先级,可以优先响应用户操作。

总结

  • useDeferredValue:用于被动延迟某个值的更新,优化显示复杂 UI 时的响应性。

  • startTransition:用于主动标记高开销更新,明确控制优先级。

  • 共同目标:避免主线程阻塞,优先处理用户交互,提升应用流畅度。

  • 选择建议

    • 若状态来自外部(如 props),用 useDeferredValue
    • 若直接控制状态更新,用 startTransition
相关推荐
念念不忘 必有回响31 分钟前
viepress:vue组件展示和源码功能
前端·javascript·vue.js
C澒37 分钟前
多场景多角色前端架构方案:基于页面协议化与模块标准化的通用能力沉淀
前端·架构·系统架构·前端框架
崔庆才丨静觅38 分钟前
稳定好用的 ADSL 拨号代理,就这家了!
前端
江湖有缘40 分钟前
Docker部署music-tag-web音乐标签编辑器
前端·docker·编辑器
早點睡39043 分钟前
高级进阶 React Native 鸿蒙跨平台开发:react-native-device-info 设备信息获取
react native·react.js·harmonyos
恋猫de小郭2 小时前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
崔庆才丨静觅8 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60619 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了9 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅9 小时前
实用免费的 Short URL 短链接 API 对接说明
前端