[前端] useEffect中不能使用async原理详解

3768 0
黑夜隐士 2022-10-21 16:14:13 | 显示全部楼层 |阅读模式
目录

    引言具体代码分析
      执行 mountEffect执行 mountEffectImpl执行 pushEffect进入到 schedulePassiveEffects推入卸载队列invokePassiveEffectCreate 执行
    hook 实现
      使用



引言

当我们尝试在 useEffect 使用 async 的时候会报错,但是一直没有了解为什么,最近在看源码,尝试从源码角度解释报错的原因。

具体代码分析


执行 mountEffect

当页面中使用 useEffect 的时候,会在初始化的时候执行 mountEffect 如下:
  1. useEffect: function(create, deps) {
  2.   currentHookNameInDev = "useEffect";
  3.   mountHookTypesDev();
  4.   checkDepsAreArrayDev(deps);
  5.   return mountEffect(create, deps);
  6. },
复制代码
执行 mountEffectImpl

执行 mountEffect 的时候执行 mountEffectImpl 如下:
  1. function mountEffectImpl(fiberFlags, hookFlags, create, deps) {
  2.   var hook = mountWorkInProgressHook();
  3.   var nextDeps = deps === void 0 ? null : deps;
  4.   currentlyRenderingFiber$1.flags |= fiberFlags;
  5.   hook.memoizedState = pushEffect(HasEffect | hookFlags, create, void 0, nextDeps);
  6. }
复制代码
执行 pushEffect

在 pushEffect 中会创建一个 effect 节点,然后添加到当前函数对应 fiber 的 updateQueue 上面,数据结构是一个环链。
  1. function pushEffect(tag, create, destroy, deps) {
  2.   var effect = {
  3.     tag,
  4.     create,
  5.     destroy,
  6.     deps,
  7.     next: null
  8.   };
  9.   var componentUpdateQueue = currentlyRenderingFiber$1.updateQueue;
  10.   if (componentUpdateQueue === null) {
  11.     componentUpdateQueue = createFunctionComponentUpdateQueue();
  12.     currentlyRenderingFiber$1.updateQueue = componentUpdateQueue;
  13.     componentUpdateQueue.lastEffect = effect.next = effect;
  14.   } else {
  15.     var lastEffect = componentUpdateQueue.lastEffect;
  16.     if (lastEffect === null) {
  17.       componentUpdateQueue.lastEffect = effect.next = effect;
  18.     } else {
  19.       var firstEffect = lastEffect.next;
  20.       lastEffect.next = effect;
  21.       effect.next = firstEffect;
  22.       componentUpdateQueue.lastEffect = effect;
  23.     }
  24.   }
  25.   return effect;
  26. }
复制代码
进入到 schedulePassiveEffects

中间又是一大堆调度,协调的逻辑,不是我们关注的重点,这里省略掉直接进入到 schedulePassiveEffects,这个函数作用是从函数组件对应的 fiber 上获取上面挂载的 effect,然后将 effect 和 fiber 堆到 pendingPassiveHookEffectsUnmount 和 pendingPassiveHookEffectsMount 这个两个队列中
  1. function schedulePassiveEffects(finishedWork) {
  2.   var updateQueue = finishedWork.updateQueue;
  3.   var lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
  4.   if (lastEffect !== null) {
  5.     var firstEffect = lastEffect.next;
  6.     var effect = firstEffect;
  7.     do {
  8.       var _effect = effect
  9.       , next = _effect.next
  10.       , tag = _effect.tag;
  11.       if ((tag & Passive$1) !== NoFlags$1 && (tag & HasEffect) !== NoFlags$1) {
  12.         //
  13.         enqueuePendingPassiveHookEffectUnmount(finishedWork, effect);
  14.         enqueuePendingPassiveHookEffectMount(finishedWork, effect);
  15.       }
  16.       effect = next;
  17.     } while (effect !== firstEffect);
  18.   }
  19. }
复制代码
推入卸载队列

这里是推入的逻辑,只展示推入挂载队列的方法,推入卸载队列是一样的
  1. function enqueuePendingPassiveHookEffectMount(fiber, effect) {
  2.   pendingPassiveHookEffectsMount.push(effect, fiber);
  3.   if (!rootDoesHavePassiveEffects) {
  4.     rootDoesHavePassiveEffects = true;
  5.     scheduleCallback(NormalPriority$1, function() {
  6.       flushPassiveEffects();
  7.       return null;
  8.     });
  9.   }
  10. }
复制代码
invokePassiveEffectCreate 执行

之后又是一大推调度,协调的逻辑,等待协调执行完毕后,之后会进入 flushPassiveEffectsImpl ,函数太长了,只贴出相关的部分,逻辑是循环挂载 effect 队列中的每一个 effect 传入到 invokePassiveEffectCreate 执行
  1. // ...
  2. var mountEffects = pendingPassiveHookEffectsMount;
  3. pendingPassiveHookEffectsMount = [];
  4. for (var _i = 0; _i < mountEffects.length; _i += 2) {
  5.   var _effect2 = mountEffects[_i];
  6.   var _fiber = mountEffects[_i + 1];
  7.   {
  8.     setCurrentFiber(_fiber);
  9.     {
  10.       invokeGuardedCallback(null, invokePassiveEffectCreate, null, _effect2);
  11.     }
  12.     if (hasCaughtError()) {
  13.       if (!(_fiber !== null)) {
  14.         {
  15.           throw Error("Should be working on an effect.");
  16.         }
  17.       }
  18.       var _error4 = clearCaughtError();
  19.       captureCommitPhaseError(_fiber, _error4);
  20.     }
  21.     resetCurrentFiber();
  22.   }
  23. }
  24. // ...
复制代码
这个函数会获取 create 并执行,然后将执行结果挂载到 destroy 上,这里的 create 就是 useEffect 中的第一个参数,从这里可以看出,如果有返回值,那么 destroy 就是第一个函数的返回值,没有就是 undefined
  1. function invokePassiveEffectCreate(effect) {
  2.   var create = effect.create;
  3.   effect.destroy = create();
  4. }
复制代码
卸载的时候会通过函数组件对应的 fiber 获取 effect 链表,然后遍历链表,获取环链上的每一个节点,如果 destroy 不是 undefined 就执行,所以如果 useEffect 第一个参数传入 async, 那么这里的 destroy 就是一个 promise 对象,对象是不能执行的,所以报错。
  1. function commitHookEffectListUnmount(tag, finishedWork) {
  2.   var updateQueue = finishedWork.updateQueue;
  3.   var lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
  4.   if (lastEffect !== null) {
  5.     var firstEffect = lastEffect.next;
  6.     var effect = firstEffect;
  7.     do {
  8.       if ((effect.tag & tag) === tag) {
  9.         // Unmount
  10.         var destroy = effect.destroy;
  11.         effect.destroy = undefined;
  12.         if (destroy !== undefined) {
  13.           destroy();
  14.         }
  15.       }
  16.       effect = effect.next;
  17.     } while (effect !== firstEffect);
  18.   }
  19. }
复制代码
既然知道了原因那么,解决方案就非常简单,直接手写一个自定义 hook,包裹一下就可以处理这个问题了,hook 实现如下。

hook 实现
  1. import { useEffect } from 'react'
  2. export default function useAsyncEffect<T, U extends any[]>(
  3.   method: () => Promise<T>,
  4.   deps: U
  5. ) {
  6.   useEffect(() => {
  7.     (async () => {
  8.       await method()
  9.     })()
  10.   }, deps)
  11. }
复制代码
使用
  1. import React, { useState } from 'react'
  2. import { useAsyncEffect } from './useAsyncEffect'
  3. export default function Demo() {
  4.   const [count, setCount] = useState(0)
  5.   function fetchData(): Promise<number> {
  6.     return new Promise((resolve) => {
  7.       setTimeout(() => {
  8.         resolve(count + 1)
  9.       }, 2000)
  10.     })
  11.   }
  12.   useAsyncEffect(async () => {
  13.     const count = await fetchData()
  14.     setCount(count)
  15.   }, [fetchData])
  16.   return (
  17.     <div>{count}</div>
  18.   )
  19. }
复制代码
这里其实有问题,因为返回值永远是undefined,你可以开动脑筋尝试修复一下。
以上就是对 useEffect 中为啥不能使用 async 的简单分析,有不足之处,欢迎提出,更多关于useEffect不能使用async的资料请关注中国红客联盟其它相关文章!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

中国红客联盟公众号

联系站长QQ:5520533

admin@chnhonker.com
Copyright © 2001-2026 Discuz Team. Powered by Discuz! X3.5 ( 粤ICP备13060014号 )|天天打卡 本站已运行