useEffect使用async报错怎么回事,该怎么办
Admin 2022-08-06 群英技术资讯 1548 次浏览
关于“useEffect使用async报错怎么回事,该怎么办”的知识有一些人不是很理解,对此小编给大家总结了相关内容,具有一定的参考借鉴价值,而且易于学习与理解,希望能对大家有所帮助,有这个方面学习需要的朋友就继续往下看吧。当我们尝试在 useEffect 使用 async 的时候会报错,但是一直没有了解为什么,最近在看源码,尝试从源码角度解释报错的原因。
当页面中使用 useEffect 的时候,会在初始化的时候执行 mountEffect 如下:
useEffect: function(create, deps) {
currentHookNameInDev = "useEffect";
mountHookTypesDev();
checkDepsAreArrayDev(deps);
return mountEffect(create, deps);
},
执行 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 中会创建一个 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,这个函数作用是从函数组件对应的 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;
});
}
}
之后又是一大推调度,协调的逻辑,等待协调执行完毕后,之后会进入 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 实现如下。
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,你可以开动脑筋尝试修复一下。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:mmqy2019@163.com进行举报,并提供相关证据,查实之后,将立刻删除涉嫌侵权内容。
猜你喜欢
网站页面的响应速度与用户体验息息相关,直接影响到用户是否愿意继续访问你的网站,所以这篇文章主要给大家介绍了关于Vue项目打包、合并及压缩优化网页响应速度的相关资料,需要的朋友可以参考下
这篇文章主要给大家分享的是JavaScript回调函数的相关内容,回调用于数组,计时器函数,promise,事件处理程序等中,是JavaScript学习中需要掌握的内容,对此本文分享给大家做个参考。另外,一些朋友对于同步和异步回调不是很理解,文中也有很详细的介绍,感兴趣的朋友就继续往下看吧。
这篇文章给大家分享的是JS中删除数组第一个元素的方法。下文给大家介绍了三种方法,分别是使用shift()函数、使用delete运算符和使用splice()函数,文中的示例代码介绍得很详细,有需要的朋友可以参考,接下来就跟随小编一起了解看看吧。
这篇文章我们来了解JavaScript全局函数的相关内容,JavaScript中全局函数有很多,例如isNaN()用于检查某个值是否是数字、Number()用于把对象的值转换为数字等等,接下来我们就来认识一下这些全局函数,以及应该怎样使用,下文有很详细的介绍,有需要的朋友可以参考,接下来就跟随小编来一起学习一下吧!
js浏览器窗口大小改变事件使用到window.onresize事件,使用简单,一般监听浏览器窗口大小是为了做html页面自适应功能,下面来看看简单使用示例吧。 onresize事件使用示例 window.onresize=function(){} html页面自适应中使用到的完整代码如下 scriptwindow.o
成为群英会员,开启智能安全云计算之旅
立即注册关注或联系群英网络
7x24小时售前:400-678-4567
7x24小时售后:0668-2555666
24小时QQ客服
群英微信公众号
CNNIC域名投诉举报处理平台
服务电话:010-58813000
服务邮箱:service@cnnic.cn
投诉与建议:0668-2555555
Copyright © QY Network Company Ltd. All Rights Reserved. 2003-2020 群英 版权所有
增值电信经营许可证 : B1.B2-20140078 ICP核准(ICP备案)粤ICP备09006778号 域名注册商资质 粤 D3.1-20240008