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

2025-06-05
16756 分钟
...

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


核心概念解析

1. useDeferredValue:延迟值更新

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

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

  • 机制

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

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

import React, { useState, useDeferredValue, useMemo } from 'react';


const largeList = Array.from({ length: 10000 }, (_, i) => ({
  id: i,
  name: `User ${i}`,
}));

function ExpensiveList({ filter }) {
  
  const filteredList = useMemo(() => {
    
    const start = performance.now();
    while (performance.now() - start < 10) {} 
    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('');
  
  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 优先处理高优先级事件(如用户输入),过渡任务可被中断或延迟。
  • 示例

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

import React, { useState, useTransition } from 'react';


function ExpensiveList({ users }) {
  
  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:渲染过滤后的用户列表,假设渲染很多数据,比较耗时。

对比

特性useDeferredValuestartTransition
控制对象值(如 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

如果您觉得这篇文章有帮助,请点个赞吧~

分享文章

相关文章

更多文章 →
react2025-12-02
useEffect与useLayoutEffect对比
在 React Hooks 中, 和 都用于处理副作用逻辑,例如订阅、事件监听、DOM 操作等。但它们有着本质性的执行时机差异,这影响到页面渲染、布局抖动、性能表现等关键点。 1\. 执行时机的核心区别 在 React 的渲染流程中: 1. 渲染(Render phase) React 根据 state/props 计算 UI,生成虚拟 DOM,不访问真实 DOM。 2. 提交(Commit phase) React 将虚拟 DOM 变...
学习
react2025-08-03
在 React 中实现倒计时功能会有什么坑
倒计时 倒计时是一个非常常见的业务场景,但是在 React 中实现起来,却不算简单。 首先我们来看这段倒计时代码,它能否正常执行? 来看看实际表现效果 可以看到计时器在不断执行,但是 的值却没有变。 这是一个很经典的问题:React 闭包陷阱 。 我们来详细分析下: 的依赖数组 是空的,这意味着 effect 只会在组件挂载时执行一次,而不会在 状态更新时重新执行。所以 回调函数中捕获的 值始终是初始值 。 这个问题解决起来也很简单,有...
学习面试
react2025-07-30
React性能优化三剑客:memo、useMemo和useCallback详解
前言 在React开发中,性能优化是一个永恒的话题。今天我们就来深入探讨React提供的三个重要性能优化工具: 、 和 ,它们如何帮助我们构建更高效的React应用。 1\. 为什么需要性能优化? 🤔 React的核心机制是当组件的state或props发生变化时,组件会重新渲染。但有时这种重新渲染是不必要的: 父组件更新导致所有子组件重新渲染 ,即使子组件的props没有变化 复杂计算在每次渲染时重复执行 ,消耗大量资源 函数引用在...
学习面试
react2024-12-18
Antd 样式覆盖
&nbsp; 目前作者所在的业务正在升级 Antd5.0,不得不说 Antd5.0 真的太香了,但是由于团队的 UE 规范进行了大改版,Antd5.0 所有的 必须对齐最新的规范,因此就需要对 Antd5.0 组件做主题定制。 现状 目前针对项目中使用的 antd 高频组件进行了梳理,并且根据规范基于 design token 对这些高频组件进行样式定制,对于主题色、圆角、边框、字体等用户 高的主题都已经满足团队规范。 虽然 Desig...
学习
react2024-12-06
React 19 终于发布,一大波新功能正式升级
这篇文章主要介绍了 React 19 的新功能,包括 Actions 自动处理数据突变相关状态,新增的 useActionState、useFormStatus、useOptimistic 等 hook,新的 use API 读取渲染资源,新的 React DOM 静态 API 生成静态站点,以及 React 服务器组件中的 RSC 和 RSA 等内容。同时提到后续会分享针对旧版功能的优化和改良。 关联问题: React 19 性能如何...
学习面试
react2024-10-11
React表单:formik、final-form和react-hook-form
表单无处不在,它是每个网站的必备部分。在用React构建web应用时,处理表单是不可避免的。 你可以选择自己的方式来处理,或者选择社区中现成的库。然而,当你选择一个第三方库时,你会立即面临一个问题:有太多的库可供选择。 选择一个库是一件棘手的事情,随意选择一个GitHub上星标多的库并不总是明智的。你肯定需要阅读文档、代码示例,并多次尝试,才能确定这是你想要用于自己项目的库。 对于处理表单,我们有以下选择: | 库名称 | GitHub...
学习

评论

请登录后发表评论

去登录
加载评论中...

目录