useDeferredValue 和 useTransition

干嘛用的

useDeferred的注释了写明了使用路径 用户输入获取数据

加上官网也写了useDeferredValue是一个和Suspense结合使用的方法。

Suspense

我们可以先回顾一下<Suspense>如何使用的

父组件

javascript 复制代码
const Demo = () => {
  const [value, setValue] = useState("");
  return (
    <>
      <input
        value={value}
        onChange={(v) => {
          setValue(v.target.value);
        }}
      />
      <Suspense fallback={<>loading</>}>
        <SearchResults value={value} />
      </Suspense>
    </>
  );
};

子组件

javascript 复制代码
function SearchResults(props: { value: string }) {
  const random = use(fetchData(props.value));
  return <div>{random + "  " + props.value}</div>;
}

use钩子

use钩子接受一个promise对象,在本文中promise对象来自下面的fetchData方法。在use中,我们会为这个promise对象增加三个属性,这三个属性分别就对应了promise本身的状态。当我们的promise的状态不为fulfilled时,就向外throw promise或者error,来触发外层的suspense渲染fallback。当promise的状态为fulfilled时,则返回promise实际的value。

typescript 复制代码
function use(
  promise: Promise<unknown> & {
    status: string;
    value: unknown;
    reason: unknown;
  }
) {
  if (promise.status === "fulfilled") {
    return promise.value;
  } else if (promise.status === "rejected") {
    throw promise.reason;
  } else if (promise.status === "pending") {
    throw promise;
  } else {
    promise.status = "pending";
    promise.then(
      (result) => {
        promise.status = "fulfilled";
        promise.value = result;
      },
      (reason) => {
        promise.status = "rejected";
        promise.reason = reason;
      }
    );
    throw promise;
  }
}

fetchData方法

我们通过延迟三秒来模拟接口返回的情况,在实际的业务场景中,就是我们请求数据的方法

typescript 复制代码
const map = new Map();
const fetchData = (str: string) => {
  if (!map.has(str)) {
    map.set(
      str,
      new Promise((resolve) => setTimeout(() => resolve(Math.random()), 3000))
    );
  }
  return map.get(str);
};

简单来说,我们通过use钩子对请求方法返回的promise进行二次包装,目的就是为了当promise处于未完成的状态时,保证<Suspense>渲染fallback。<Suspense>这个组件本身会在其children第一次throw promise时,为这个promise注册一个回调,在promise完成时重新渲染children

知道了Suspense怎么用,我们再看看如何结合deferredValue。

useDeferredValue

其实非常简单,我们修改一下父组件看看效果。

ini 复制代码
const Deferred = () => {
  const [value, setValue] = useState("");
  const deferred = useDeferredValue(value);
  return (
    <>
      <input
        value={value}
        onChange={(v) => {
          setValue(v.target.value);
        }}
      />
      <Suspense fallback={<>fallback</>}>
        <SearchResults value={deferred} />
      </Suspense>
    </>
  );
};

随着我们的输入,不断发起请求,然后进入到fallBack中

可以看到随着用户输入不再会渲染Suspense的fallback了。可以理解为用户的输入的优先级较高,因此input中的onChange会高优先级执行,导致了函数组件的重新执行,但是deferred由于被useDeferredValue包裹,因此会延迟更新,通过下面的动图可以看到每次输入都会引起组件的更新,但是并不会引起deferred的值的更新。当我们停止输入后最终deferredvalue的值同步了。

这时候会发现,把Suspense去掉也是一样的效果。其实是react帮我们兜底了,大家可以试一下替换SearchResults。当我们向外throw promise时,react也会帮我们进行处理,这个行为与Suspense无关

javascript 复制代码
let a = true;
export default function SearchResults(props: { value: string }) {
  // const random = use(fetchData(props.value));
  if (a) {
    throw new Promise((res) =>
      setTimeout(() => {
        a = false;
        res(1999);
      }, 5000)
    );
  }

  return <div>{"random" + "  " + props.value}</div>;
}

useTransition

useTransition则不需要传递参数,返回[isPending, setTransition]其中setTransition使用时需要传递一个函数,这个函数会以低优先级的方式执行,并且具有合并执行的特效(多次调用只生效一次)。乍一听和useDeferredValue类似。

实际使用时setTransition则更加灵活。因为我们在使用useDeferredValue时,react帮我们决定了何时为这个deferred值附新值,但是我们可以通过setTransition来直接控制何时进行低优先级操作。而useDeferredValue的使用场景更多的是原值在组件中不可控,比如说我们上面的例子中子组件接受props,把props通过useDeferredValue进行包裹,因为props的控制权不在子组件中。

同样的,受控组件的onchange也最好使用useState的set方法。

ini 复制代码
const Transition = () => {
  const [value, setValue] = useState("");
  const [transedValue, setTransedValue] = useState("");
  const [, setTransition] = useTransition();
  return (
    <>
      <input
        value={value}
        onChange={(v) => {
          setValue(v.target.value);
          setTransition(() => {
            setTransedValue(v.target.value);
          });
        }}
      />
      <Suspense fallback={<>fallback</>}>
        <SearchResults value={transedValue} />
      </Suspense>
    </>
  );
};

上面这段代码和使用useDeferredValue时的效果应该是一样的

接下来再看一下useTransition的批处理特性。

ini 复制代码
const Transition = () => {
  const [value, setValue] = useState("");
  const [transedValue, setTransedValue] = useState("");
  const [, setTransition] = useTransition();
  return (
    <>
      <input
        value={value}
        onChange={(v) => {
          setTransition(() => {
            setValue(v.target.value);
            setTransedValue(v.target.value);
          });
        }}
      />
      <Suspense fallback={<>fallback</>}>
        <SearchResults value={transedValue} />
      </Suspense>
    </>
  );
};

可以看到我们不停的输入,但是由于批处理,很多输入被忽略了。这也是为什么受控组件不适合useTransition的原因。但是如果我们在setTransition中增加一些输出,会发现输出并没有被合并,因为函数其实是照常执行的,只是如果在执行时有高优先级的任务,那么会被高优先级的任务中断。

相关链接:react.dev/reference/r... react.dev/reference/r...

相关推荐
酒尘&5 小时前
JS数组不止Array!索引集合类全面解析
开发语言·前端·javascript·学习·js
学历真的很重要5 小时前
VsCode+Roo Code+Gemini 2.5 Pro+Gemini Balance AI辅助编程环境搭建(理论上通过多个Api Key负载均衡达到无限免费Gemini 2.5 Pro)
前端·人工智能·vscode·后端·语言模型·负载均衡·ai编程
用户47949283569156 小时前
"讲讲原型链" —— 面试官最爱问的 JavaScript 基础
前端·javascript·面试
用户47949283569156 小时前
2025 年 TC39 都在忙什么?Import Bytes、Iterator Chunking 来了
前端·javascript·面试
大怪v7 小时前
【Virtual World 04】我们的目标,无限宇宙!!
前端·javascript·代码规范
狂炫冰美式8 小时前
不谈技术,搞点文化 🧀 —— 从复活一句明代残诗破局产品迭代
前端·人工智能·后端
xw59 小时前
npm几个实用命令
前端·npm
!win !9 小时前
npm几个实用命令
前端·npm
代码狂想家9 小时前
使用openEuler从零构建用户管理系统Web应用平台
前端
dorisrv10 小时前
优雅的React表单状态管理
前端