资讯专栏INFORMATION COLUMN

useEffect支持async及await如何运用

3403771864 / 267人阅读

       背景

  在使用useEffect中用啦回调函数中使用 async...await... 这时候就会报错。

1.png

  上面代码可以看到,在报错,effect function 应该返回一个销毁函数(effect:是指return返回的cleanup函数),如果 useEffect 第一个参数传入 async,返回值则变成了 Promise,结果就是会导致 react 在调用销毁函数的时候报错。

  React 为什么要这么做?

  useEffect 作为 Hooks 中一个很重要的 Hooks,它能够让你在函数组件中执行副作用操作。 也能够完成之前 Class Component 中的生命周期的职责。它返回的函数的执行时机如下:

  首次渲染不会进行清理,会在下一次渲染,清除上一次的副作用。

  卸载阶段也会执行清除操作。

  不管怎样,返回值是异步,这样我们无法预知代码的执行情况,很容易出现难以定位的 Bug。所以 React 就直接限制了不能 useEffect 回调函数中不能支持 async...await...

  useEffect 怎么支持 async...await...

  竟然 useEffect 的回调函数不能使用 async...await,那我直接在它内部使用。

  做法一:创建一个异步函数(async...await 的方式),然后执行该函数。

  useEffect(() => {
  const asyncFun = async () => {
  setPass(await mockCheck());
  };
  asyncFun();
  }, []);

  做法二:也可以使用 IIFE,如下所示:

  useEffect(() => {
  (async () => {
  setPass(await mockCheck());
  })();
  }, []);

  自定义 hooks

  现在知道解决方法,我们完全可以将其封装成一个 hook,是不是更优雅。现在看下 ahooks 的 useAsyncEffect,它支持所有的异步写法,包括 generator function。

  思路跟上面一样,入参跟 useEffect 一样,一个回调函数(不过这个回调函数支持异步),另外一个依赖项 deps。内部还是 useEffect,将异步的逻辑放入到它的回调函数里面。

  function useAsyncEffect(
  effect: () => AsyncGenerator<void, void, void> | Promise<void>,
  // 依赖项
  deps?: DependencyList,
  ) {
  // 判断是 AsyncGenerator
  function isAsyncGenerator(
  val: AsyncGenerator<void, void, void> | Promise<void>,
  ): val is AsyncGenerator<void, void, void> {
  // Symbol.asyncIterator: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Symbol/asyncIterator
  // Symbol.asyncIterator 符号指定了一个对象的默认异步迭代器。如果一个对象设置了这个属性,它就是异步可迭代对象,可用于for await...of循环。
  return isFunction(val[Symbol.asyncIterator]);
  }
  useEffect(() => {
  const e = effect();
  // 这个标识可以通过 yield 语句可以增加一些检查点
  // 如果发现当前 effect 已经被清理,会停止继续往下执行。
  let cancelled = false;
  // 执行函数
  async function execute() {
  // 如果是 Generator 异步函数,则通过 next() 的方式全部执行
  if (isAsyncGenerator(e)) {
  while (true) {
  const result = await e.next();
  // Generate function 全部执行完成
  // 或者当前的 effect 已经被清理
  if (result.done || cancelled) {
  break;
  }
  }
  } else {
  await e;
  }
  }
  execute();
  return () => {
  // 当前 effect 已经被清理
  cancelled = true;
  };
  }, deps);
  }

  async...await 其实之前就有说道,重点看看实现中变量 cancelled 的实现的功能。 它的作用是中断执行。

  通过 yield 语句可以增加一些检查点,如果发现当前 effect 已经被清理,会停止继续往下执行。

  现在我们试想,用户频繁的操作,可能现在这一轮操作 a 执行还没完成,就已经开始开始下一轮操作 b。这个时候,操作 a 的逻辑已经失去了作用了,那么我们就可以停止往后执行,直接进入下一轮操作 b 的逻辑执行。这个 cancelled 就是用来取消当前正在执行的一个标识符。

  还可以支持 useEffect 的清除机制么?

  可以看到上面的 useAsyncEffect,内部的 useEffect 返回函数只返回了如下:

  return () => {
  // 当前 effect 已经被清理
  cancelled = true;
  };

  上面其实就是说明,在通过 useAsyncEffect 没有 useEffect 返回函数中执行清除副作用的功能。

  你猜想将 effect(useAsyncEffect的回调函数)的结果,放入到 useAsyncEffect 中不就可以了?

  实现最终类似如下:

  function useAsyncEffect(effect: () => Promise<void | (() => void)>, dependencies?: any[]) {
  return useEffect(() => {
  const cleanupPromise = effect()
  return () => { cleanupPromise.then(cleanup => cleanup && cleanup()) }
  }, dependencies)
  }

  上面方法很多大神读赞成:

2.png

  上面不是延迟机制,是一种取消机制。否则,在钩子已经被取消之后,回调函数仍然有机会对外部状态产生影响。他的实现和例子我也贴一下,跟 useAsyncEffect 其实思路是一样的,如下:

  实现:

  function useAsyncEffect(effect: (isCanceled: () => boolean) => Promise<void>, dependencies?: any[]) {
  return useEffect(() => {
  let canceled = false;
  effect(() => canceled);
  return () => { canceled = true; }
  }, dependencies)
  }

  Demo:

  useAsyncEffect(async (isCanceled) => {
  const result = await doSomeAsyncStuff(stuffId);
  if (!isCanceled()) {
  // TODO: Still OK to do some effect, useEffect hasn't been canceled yet.
  }
  }, [stuffId]);

  其实归根结底,我们的清除机制不应该依赖于异步函数,否则很容易出现难以定位的 bug。

  总结与思考

  在代码运行中由于 useEffect 是在函数式组件中承担执行副作用操作的职责,它的返回值的执行操作应该是可以预期的,而不能是一个异步函数,所以不支持回调函数 async...await 的写法。

  我们可以将 async...await 的逻辑封装在 useEffect 回调函数的内部,这就是 ahooks useAsyncEffect 的实现思路,而且它的范围更加广,它支持的是所有的异步函数,包括generator function。

      本文内容已结束,欢迎阅读更多精彩内容。


文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/128371.html

相关文章

  • 在 React Hooks 中如何请求数据?

    摘要:现在,请求数据和查询参数两个相互独立,但是我们需要像一个办法希望他们耦合起来,只获取输入框输入的参数指定的话题文章。好了,现在一旦你改变输入框内容,数据就会重新获取。 showImg(https://segmentfault.com/img/remote/1460000018652592?w=1024&h=683); 通过这个教程,我想告诉你在 React 中如何使用 state 和 ...

    snowell 评论0 收藏0
  • 【译】如何在React Hooks中获取数据?

    摘要:在这种情况下,如果状态发生变化,将再次运行以从获取数据。你可以在内做到在表单中获取数据到目前为止,我们只有和按钮的组合。现在,在获取数据时,可以使用向函数发送信息。例如,在成功请求的情况下,用于设置新状态对象的数据。 原文链接: https://www.robinwieruch.de/r... 在本教程中,我想通过state和effect hook来像你展示如何用React Hook...

    habren 评论0 收藏0
  • 「每日一瞥

    摘要:首先,我们需要一个基本框架来处理表单域变化和表格提交。最起码我们需要提供一个来告诉如果用户还没有对表单域进行改动,就不必展示错误。我们需要一个来标识用户已尝试提交表单,还需要来标识表单是否正在提交以及每个表单域是否正在进行异步校验。 showImg(https://segmentfault.com/img/remote/1460000017516912?w=1200&h=630); ...

    XboxYan 评论0 收藏0
  • React Hooks 入门(2019)

    摘要:到目前为止,表达这种流程的基本形式是课程。按钮依次响应并更改获取更新的文本。事实证明不能从返回一个。可以在组件中使用本地状态,而无需使用类。替换了提供统一,和。另一方面,跟踪中的状态变化确实很难。 备注:为了保证的可读性,本文采用意译而非直译。 在这个 React钩子 教程中,你将学习如何使用 React钩子,它们是什么,以及我们为什么这样做! showImg(https://segm...

    GitCafe 评论0 收藏0
  • 解析React useEffect支持async function示例

      useEffect是很常见的,现在写的是十分需求的。  useEffect(async()=>{   awaitgetPoiInfo();//请求数据   },[]);  可是React 却无法支持这样做,就是因为 effect function 应该返回一个销毁函数(effect:是指return返回的cleanup函数),如果 useEffect 第一个参数传入 async,返回值则...

    3403771864 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<