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...

相关推荐
EricWang135812 分钟前
[OS] 项目三-2-proc.c: exit(int status)
服务器·c语言·前端
September_ning12 分钟前
React.lazy() 懒加载
前端·react.js·前端框架
web行路人22 分钟前
React中类组件和函数组件的理解和区别
前端·javascript·react.js·前端框架
超雄代码狂44 分钟前
ajax关于axios库的运用小案例
前端·javascript·ajax
长弓三石1 小时前
鸿蒙网络编程系列44-仓颉版HttpRequest上传文件示例
前端·网络·华为·harmonyos·鸿蒙
小马哥编程1 小时前
【前端基础】CSS基础
前端·css
嚣张农民1 小时前
推荐3个实用的760°全景框架
前端·vue.js·程序员
周亚鑫2 小时前
vue3 pdf base64转成文件流打开
前端·javascript·pdf
Justinc.2 小时前
CSS3新增边框属性(五)
前端·css·css3
neter.asia2 小时前
vue中如何关闭eslint检测?
前端·javascript·vue.js