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

相关推荐
Myli_ing38 分钟前
HTML的自动定义倒计时,这个配色存一下
前端·javascript·html
dr李四维1 小时前
iOS构建版本以及Hbuilder打iOS的ipa包全流程
前端·笔记·ios·产品运营·产品经理·xcode
雯0609~1 小时前
网页F12:缓存的使用(设值、取值、删除)
前端·缓存
℘团子এ1 小时前
vue3中如何上传文件到腾讯云的桶(cosbrowser)
前端·javascript·腾讯云
学习前端的小z1 小时前
【前端】深入理解 JavaScript 逻辑运算符的优先级与短路求值机制
开发语言·前端·javascript
彭世瑜2 小时前
ts: TypeScript跳过检查/忽略类型检查
前端·javascript·typescript
FØund4042 小时前
antd form.setFieldsValue问题总结
前端·react.js·typescript·html
Backstroke fish2 小时前
Token刷新机制
前端·javascript·vue.js·typescript·vue
小五Five2 小时前
TypeScript项目中Axios的封装
开发语言·前端·javascript
小曲程序2 小时前
vue3 封装request请求
java·前端·typescript·vue