React Hook useTransition

一、核心定位:解决 “优先级错配” 的渲染优化 Hook

useDeferredValue 是 React 提供的并发渲染优化工具,核心作用是生成一个 “延迟版的状态值”,让非紧急 UI 更新(如长列表)延后执行,优先保证用户直接交互(如输入、点击)的实时响应,从而解决 “大量节点渲染阻塞交互” 的卡顿问题。

它的本质是:分离状态更新优先级,把 “用户感知强的紧急更新” 和 “耗时的非紧急更新” 错开,避免 JS 引擎被占用导致的卡顿。

memo + deferredValue 的组合,本质让长列表组件只 “响应延迟状态的变化”,而不响应输入框的实时变化

二、基础用法与关键特性

1. 基础语法

import { useDeferredValue, useState } from "react";

function App() {
  // 1. 声明紧急状态(如输入框值)
  const [value, setValue] = useState("");
  // 2. 生成延迟状态(非紧急 UI 用这个值)
  const deferredValue = useDeferredValue(value);

  return (
    <div>
      {/* 紧急 UI:用原始状态,实时响应 */}
      <input value={value} onChange={(e) => setValue(e.target.value)} />
      {/* 非紧急 UI:用延迟状态,延后更新 */}
      <LongList data={deferredValue} />
    </div>
  );
}

2. 两个关键特性

  • 自动合并更新:用户快速操作(如连续输入 1→2→3)时,deferredValue 会跳过中间值,直接更新为最终值(如 123),避免非紧急 UI 反复渲染。
  • 不阻塞紧急更新deferredValue 的更新会被标记为 “低优先级”,只有当浏览器处理完紧急更新(如输入框显示)并空闲时,才会执行非紧急 UI 的更新。

三、必须配合的 “生效条件”

useDeferredValue 单独使用时效果有限,必须配合以下两个条件才能完全发挥作用,否则会被 “无效协调” 抵消:

1. 非紧急 UI 需独立抽离为组件

将长列表等耗时 UI 从主组件(如 App)中抽离为独立组件(如 LongList),避免 “紧急更新触发整棵组件树的全量协调”。反例:若长列表与输入框在同一组件,即使使用 deferredValue,React 仍会遍历所有节点,导致无效协调。

2. 配合 memo 阻止无效重渲染

memo 包裹独立的非紧急组件,让组件仅在延迟状态(deferredValue)变化时才重渲染,避免 “父组件更新导致非紧急组件反复协调”。

// 1. 抽离长列表组件,并用 memo 包裹
const LongList = memo((props: { data: string }) => {
  return (
    <div className="list">
      {/* 50000 个节点:仅在 props.data(deferredValue)变化时重渲染 */}
      {Array(50000)
        .fill("")
        .map((_, idx) => (
          <div key={idx}>{props.data}</div>
        ))}
    </div>
  );
});

// 2. 主组件中使用延迟状态
function App() {
  const [value, setValue] = useState("");
  const deferredValue = useDeferredValue(value); // 延迟状态

  return (
    <div>
      <input value={value} onChange={(e) => setValue(e.target.value)} />
      <LongList data={deferredValue} /> {/* 非紧急 UI 用延迟状态 */}
    </div>
  );
}

四、核心原理:为什么能解决卡顿?

卡顿的根源是 “大量节点的协调(reconcile)过程占用 JS 引擎”,useDeferredValue 通过以下两步从根本上优化:

1. 第一步:延迟非紧急 UI 的协调时机

  • 紧急更新(输入框):value 变化时,输入框立即协调(仅 1 个节点,速度快),优先响应用户输入;
  • 非紧急更新(长列表):deferredValue 延迟更新,长列表的协调过程被延后到浏览器空闲时执行,不阻塞输入。

2. 第二步:合并中间更新减少协调次数

用户快速输入时,deferredValue 不跟随每次 value 变化,而是合并为最终值后一次性更新,让长列表只协调 1 次,而非多次反复协调(如输入 1→2→3,只在 123 时协调 1 次)。

五、常见误区与注意事项

1. 认为 “用了就生效”,不抽离组件 / 不用 memo

  • 问题:若长列表与输入框在同一组件,或未用 memo,React 仍会频繁协调大量节点,deferredValue 被无效协调抵消;
  • 解决:必须 “组件抽离 + memo 缓存”,精准控制非紧急 UI 的更新时机。memo缓存的子组件不会因为父组件更新而重新渲染,因而依赖props为defferedValue的长列表组件不会跟随进行协调。

2. 延迟状态会导致 “视觉闪烁”

  • 实际:deferredValue 会保留旧值直到更新,非紧急 UI 不会空白或闪烁,只是更新滞后,用户感知为 “输入实时,列表慢一点”,属于可接受的体验。

3. 注意:只优化 “渲染卡顿”,不优化 “数据计算”

useDeferredValue 仅优化 “UI 协调与渲染” 的优先级,若卡顿源于 “大量数据计算”(如复杂排序),需配合 useMemo 优化计算结果,二者分工不同。

六、适用场景与对比

1. 核心适用场景

  • 输入框 + 长列表(如搜索框实时联想);
  • 表单输入 + 实时图表渲染;
  • 任何 “用户交互需实时响应,同时伴随大量节点渲染” 的场景。

2. 与 useTransition 的区别

特性useDeferredValueuseTransition
核心作用生成延迟状态值,控制 UI 依赖标记状态更新为低优先级,批量延迟
适用场景基于状态值的 UI 优先级分离,接受父组件的传递的值得延迟更新批量低优先级更新(如按钮触发列表刷新,用户交互触发得状态更新)
依赖配合需配合 memo + 组件抽离可单独使用,无需额外依赖
主动被动被动延迟值的更新主动标记低优先级的更新

七、手写模拟useDefferedValue实现

export default function App() {
  const [value, setValue] = useState("");
  const [isPending, startTransition] = useTransition();
  const [deferredValue, setDeferredValue] = useState("");

  useEffect(() => {
    startTransition(() => setDeferredValue(value));
  }, [value, startTransition]);

  const handleChange = (e) => {
    setValue(e.target.value);
  };

  return (
    <div>
      <input value={value} onChange={handleChange} />
      <LongList value={deferredValue} />
    </div>
  );
}

const LongList = memo((props) => {
  useLayoutEffect(() => {
    // 浏览器渲染前移除大量的 dom 节点,排除浏览器渲染大量节点的影响
    var container = document.getElementsByClassName("container");
    var list = document.getElementsByClassName("list");
    if (list.length) {
      container[0].removeChild(list[0]);
    }
  });

  return (
    <div className="container">
      {Array(100)
        .fill("a")
        .map((item) => (
          <div>{props.value}</div>
        ))}
      <div className="list">
        {Array(50000)
          .fill("a")
          .map((item) => (
            <div>{props.value}</div>
          ))}
      </div>
    </div>
  );
});
← 返回列表