useEffect中不能使用async原理详解

useEffect中不能使用async原理详解

目录

引言

具体代码分析

执行 mountEffect

执行 mountEffectImpl

执行 pushEffect

进入到 schedulePassiveEffects

推入卸载队列

invokePassiveEffectCreate 执行

hook 实现

使用

引言

当我们尝试在 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的资料请关注易知道(ezd.cc)其它相关文章!

推荐阅读