目录
引言具体代码分析
执行 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的资料请关注中国红客联盟其它相关文章! |