useDeferredValue 和 useTransition

干嘛用的

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

useDeferred的函数签名

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

来自官网

Suspense

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

父组件

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

子组件

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。

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方法

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

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

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

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无关

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方法。

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的批处理特性。

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…

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYYpQLOX' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片