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
相关推荐
爱分享的程序员25 分钟前
前端面试专栏-算法篇:18. 查找算法(二分查找、哈希查找)
前端·javascript·node.js
翻滚吧键盘30 分钟前
vue 条件渲染(v-if v-else-if v-else v-show)
前端·javascript·vue.js
vim怎么退出32 分钟前
万字长文带你了解微前端架构
前端·微服务·前端框架
你这个年龄怎么睡得着的32 分钟前
为什么 JavaScript 中 'str' 不是对象,却能调用方法?
前端·javascript·面试
Java水解34 分钟前
前端常用单位em/px/rem/vh/vm到底有什么区别?
前端
CAD老兵37 分钟前
Vite 如何借助 esbuild 实现极速 Dev Server 体验,并支持无 source map 的源码调试
前端
南屿im38 分钟前
JavaScript 手写实现防抖与节流:优化高频事件处理的利器
前端·javascript
Spider_Man38 分钟前
从零开始构建React天气应用:API集成与UI设计全指南 🌤️
前端·react.js
浩浩测试一下1 小时前
渗透信息收集- Web应用漏洞与指纹信息收集以及情报收集
android·前端·安全·web安全·网络安全·安全架构
西陵1 小时前
Nx带来极致的前端开发体验——借助CDD&TDD开发提效
前端·javascript·架构