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
相关推荐
Aphasia31123 分钟前
模式验证库——zod
前端·react.js
lexiangqicheng1 小时前
es6+和css3新增的特性有哪些
前端·es6·css3
拉不动的猪2 小时前
都25年啦,还有谁分不清双向绑定原理,响应式原理、v-model实现原理
前端·javascript·vue.js
烛阴2 小时前
Python枚举类Enum超详细入门与进阶全攻略
前端·python
孟孟~2 小时前
npm run dev 报错:Error: error:0308010C:digital envelope routines::unsupported
前端·npm·node.js
孟孟~2 小时前
npm install 报错:npm error: ...node_modules\deasync npm error command failed
前端·npm·node.js
狂炫一碗大米饭2 小时前
一文打通TypeScript 泛型
前端·javascript·typescript
wh_xia_jun2 小时前
在 Spring Boot 中使用 JSP
java·前端·spring boot
二十雨辰3 小时前
[HTML5]快速掌握canvas
前端·html
tingkeiii3 小时前
【react+antd+vite】优雅的引入svg和阿里巴巴图标
前端·react.js·前端框架