我的编程空间,编程开发者的网络收藏夹
学习永远不晚

useEffect中不能使用async原理详解

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

useEffect中不能使用async原理详解

引言

当我们尝试在 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,你可以开动脑筋尝试修复一下。

以上就是对 useEffect 中为啥不能使用 async 的简单分析,有不足之处,欢迎提出,更多关于useEffect不能使用async的资料请关注编程网其它相关文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

useEffect中不能使用async原理详解

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

useEffect中不能使用async的原理是什么

本篇内容介绍了“useEffect中不能使用async的原理是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!当页面中使用 useEffe
2023-07-02

es7中的async、await使用方法示例详解

async、await是es7里面的新语法,async申明一个function是异步,而await等待一个异步方法执行完成。替代promise中的then。async函数返回一个Promise对象,当函数执行的时候,一旦遇到await就会先返回,等到触发的异步操作完成,再接着执行函数体内后面的语句。
2023-01-28

RoaringBitmap原理及在Go中的使用详解

这篇文章主要为大家介绍了RoaringBitmap原理及在Go中的使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-02-28

如何在Node.js中使用async函数的方法详解

这篇文章主要为大家介绍了如何在Node.js中使用async函数的方法详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2022-12-19

Array.reduce使用原理示例详解

这篇文章主要为大家介绍了Array.reduce使用原理示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-02-27

关于Redis中bitmap的原理和使用详解

目录一、原理二、BitMap 相关命令三、BitMap 空间计算四、使用场景1. 用户签到2. 统计活跃用户(用户登陆情况)3. 统计用户在线状态4. 实现布隆过滤器五、总结一、原理先声明一下:Redis 有5种数据类型,而 BitMap
2023-05-16

详解Golang中Context的原理和使用技巧

GoContext原理和使用技巧GoContext用于传递请求级数据,提供上下文信息(如请求ID、超时、终止请求通道和值存储)。Context按照树形结构组织,父Context创建子Context并继承数据。Context可用于请求取消、超时和值存储。最佳实践包括创建特定于请求的Context、使用Context值存储、避免直接使用context.Background()以及使用context.Context接口。示例演示了如何使用Context进行请求取消,当请求在超时前被取消时,会输出“Request
详解Golang中Context的原理和使用技巧
2024-04-23

Spring@InitBinder注解使用及原理详解

这篇文章主要为大家介绍了Spring@InitBinder注解使用及原理详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-03-13

一文详解MySQL Join使用原理

JOIN是一种非常常见的操作,用于将两个或多个表中的数据合并到一个结果集中。MySQL支持多种JOIN类型,本文通过代码示例详细介绍了Join的使用优化,有需要的小伙伴可以参考阅读
2023-05-17

Android HandlerThread的使用及原理详解

一、HandlerThread的含义HandlerThread能够新建拥有Looper的线程。这个Looper能够用来新建其他的Handler。(线程中的Looper)需要注意的是,新建的时候需要被回调。 二、HandlerThread的用
2022-06-06

一文详解MySQL Join使用原理

目录Join的类型Joinphp原理Simpe Nested-Loop JoinIndex Nested-Loop Joinblock Nested-Loop JoinJoin优化Join的类型left join,以左表为驱动表,以左表作
2023-04-25

编程热搜

目录