资讯专栏INFORMATION COLUMN

useEffect中不能使用async缘由

3403771864 / 401人阅读

  最近尝试在 useEffect 使用 async 的时候会报错,因此,本篇文章就是想喝大家说说为什么?也解读其中缘由。

  具体代码分析

  执行 mountEffect

  当页面中使用 useEffect 的时候,会在初始化的时候执行 mountEffect 如下:

  useEffect: function(create, deps) {
  currentHookNameInDev = "useEffect";
  mountHookTypesDev();
  checkDepsAreArrayDev(deps);
  return mountEffect(create, deps);
  },

  执行 mountEffectImpl

  执行 mountEffect 的时候执行 mountEffectImpl 如下: 

 function mountEffectImpl(fiberFlags, hookFlags, create, deps) {
  var hook = mountWorkInProgressHook();
  var nextDeps = deps === void 0 ? null : deps;
  currentlyRenderingFiber$1.flags |= fiberFlags;
  hook.memoizedState = pushEffect(HasEffect | hookFlags, create, void 0, nextDeps);
  }

  执行 pushEffect

  在 pushEffect 中会创建一个 effect 节点,也要哦添加到当前函数对应 fiber 的 updateQueue 上面,数据结构是一个环链。

  function pushEffect(tag, create, destroy, deps) {
  var effect = {
  tag,
  create,
  destroy,
  deps,
  next: null
  };
  var componentUpdateQueue = currentlyRenderingFiber$1.updateQueue;
  if (componentUpdateQueue === null) {
  componentUpdateQueue = createFunctionComponentUpdateQueue();
  currentlyRenderingFiber$1.updateQueue = componentUpdateQueue;
  componentUpdateQueue.lastEffect = effect.next = effect;
  } else {
  var lastEffect = componentUpdateQueue.lastEffect;
  if (lastEffect === null) {
  componentUpdateQueue.lastEffect = effect.next = effect;
  } else {
  var firstEffect = lastEffect.next;
  lastEffect.next = effect;
  effect.next = firstEffect;
  componentUpdateQueue.lastEffect = effect;
  }
  }
  return effect;
  }

  进入到 schedulePassiveEffects

  直接忽略中间调度代码内容,进入到 schedulePassiveEffects,这个函数作用是从函数组件对应的 fiber 上获取上面挂载的 effect,然后将 effect 和 fiber 堆到 pendingPassiveHookEffectsUnmount 和 pendingPassiveHookEffectsMount 这个两个队列中 

 function schedulePassiveEffects(finishedWork) {
  var updateQueue = finishedWork.updateQueue;
  var lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
  if (lastEffect !== null) {
  var firstEffect = lastEffect.next;
  var effect = firstEffect;
  do {
  var _effect = effect
  , next = _effect.next
  , tag = _effect.tag;
  if ((tag & Passive$1) !== NoFlags$1 && (tag & HasEffect) !== NoFlags$1) {
  //
  enqueuePendingPassiveHookEffectUnmount(finishedWork, effect);
  enqueuePendingPassiveHookEffectMount(finishedWork, effect);
  }
  effect = next;
  } while (effect !== firstEffect);
  }
  }

  推入卸载队列

  这里是推入的逻辑,只展示推入挂载队列的方法,推入卸载队列是一样的

  function enqueuePendingPassiveHookEffectMount(fiber, effect) {
  pendingPassiveHookEffectsMount.push(effect, fiber);
  if (!rootDoesHavePassiveEffects) {
  rootDoesHavePassiveEffects = true;
  scheduleCallback(NormalPriority$1, function() {
  flushPassiveEffects();
  return null;
  });
  }
  }

  invokePassiveEffectCreate 执行

  早一大推度之后会进入 flushPassiveEffectsImpl ,函数太长了,只贴出相关的部分,逻辑是循环挂载 effect 队列中的每一个 effect 传入到 invokePassiveEffectCreate 执行

  // ...
  var mountEffects = pendingPassiveHookEffectsMount;
  pendingPassiveHookEffectsMount = [];
  for (var _i = 0; _i < mountEffects.length; _i += 2) {
  var _effect2 = mountEffects[_i];
  var _fiber = mountEffects[_i + 1];
  {
  setCurrentFiber(_fiber);
  {
  invokeGuardedCallback(null, invokePassiveEffectCreate, null, _effect2);
  }
  if (hasCaughtError()) {
  if (!(_fiber !== null)) {
  {
  throw Error("Should be working on an effect.");
  }
  }
  var _error4 = clearCaughtError();
  captureCommitPhaseError(_fiber, _error4);
  }
  resetCurrentFiber();
  }
  }
  // ...

  这个函数会获取 create 并执行,然后将执行结果挂载到 destroy 上,这里的 create 就是 useEffect 中的第一个参数,从这里可以看出,如果有返回值,那么 destroy 就是第一个函数的返回值,没有就是 undefined

  function invokePassiveEffectCreate(effect) {
  var create = effect.create;
  effect.destroy = create();
  }

  卸载的时候会通过函数组件对应的 fiber 获取 effect 链表,然后遍历链表,获取环链上的每一个节点,如果 destroy 不是 undefined 就执行,所以如果 useEffect 第一个参数传入 async, 那么这里的 destroy 就是一个 promise 对象,对象是不能执行的,所以报错。

  function commitHookEffectListUnmount(tag, finishedWork) {
  var updateQueue = finishedWork.updateQueue;
  var lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
  if (lastEffect !== null) {
  var firstEffect = lastEffect.next;
  var effect = firstEffect;
  do {
  if ((effect.tag & tag) === tag) {
  // Unmount
  var destroy = effect.destroy;
  effect.destroy = undefined;
  if (destroy !== undefined) {
  destroy();
  }
  }
  effect = effect.next;
  } while (effect !== firstEffect);
  }
  }

  知道解决方法,就好说了,直接手写一个自定义 hook,包裹一下就可以处理这个问题了,hook 实现如下。

  hook 实现

  import { useEffect } from 'react'
  export default function useAsyncEffect<T, U extends any[]>(
  method: () => Promise<T>,
  deps: U
  ) {
  useEffect(() => {
  (async () => {
  await method()
  })()
  }, deps)
  }

  使用

  import React, { useState } from 'react'
  import { useAsyncEffect } from './useAsyncEffect'
  export default function Demo() {
  const [count, setCount] = useState(0)
  function fetchData(): Promise<number> {
  return new Promise((resolve) => {
  setTimeout(() => {
  resolve(count + 1)
  }, 2000)
  })
  }
  useAsyncEffect(async () => {
  const count = await fetchData()
  setCount(count)
  }, [fetchData])
  return (
  <div>{count}</div>
  )
  }

  值得注意的是返回值永远是undefined,这就是留给大家开动脑筋解决。


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

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

相关文章

  • useEffect支持async及await如何运用

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

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

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

    GitCafe 评论0 收藏0
  • 在 React Hooks 如何请求数据?

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

    snowell 评论0 收藏0
  • 浅谈React Hooks

    摘要:另外也不利于组件的,及。所以在使用时,尽量将相关联的,会共同变化的值放入一个。有同学可能会想,每次后都会执行,这样会不会对性能造成影响。另外必须以开头来命名,这样工具才能正确检测其是否符合规范。 由于工作的原因我已经很长时间没接触过React了。前段时间圈子里都在讨论React Hooks,出于好奇也学习了一番,特此整理以加深理解。 缘由 在web应用无所不能的9012年,组成应用的C...

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

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

    XboxYan 评论0 收藏0

发表评论

0条评论

3403771864

|高级讲师

TA的文章

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