網(wǎng)站首頁 編程語言 正文
引言
當(dāng)我們嘗試在 useEffect 使用 async 的時(shí)候會報(bào)錯(cuò),但是一直沒有了解為什么,最近在看源碼,嘗試從源碼角度解釋報(bào)錯(cuò)的原因。
具體代碼分析
執(zhí)行 mountEffect
當(dāng)頁面中使用 useEffect 的時(shí)候,會在初始化的時(shí)候執(zhí)行 mountEffect 如下:
useEffect: function(create, deps) {
currentHookNameInDev = "useEffect";
mountHookTypesDev();
checkDepsAreArrayDev(deps);
return mountEffect(create, deps);
},
執(zhí)行 mountEffectImpl
執(zhí)行 mountEffect 的時(shí)候執(zhí)行 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);
}
執(zhí)行 pushEffect
在 pushEffect 中會創(chuàng)建一個(gè) effect 節(jié)點(diǎn),然后添加到當(dāng)前函數(shù)對應(yīng) fiber 的 updateQueue 上面,數(shù)據(jù)結(jié)構(gòu)是一個(gè)環(huán)鏈。
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;
}
進(jìn)入到 schedulePassiveEffects
中間又是一大堆調(diào)度,協(xié)調(diào)的邏輯,不是我們關(guān)注的重點(diǎn),這里省略掉直接進(jìn)入到 schedulePassiveEffects,這個(gè)函數(shù)作用是從函數(shù)組件對應(yīng)的 fiber 上獲取上面掛載的 effect,然后將 effect 和 fiber 堆到 pendingPassiveHookEffectsUnmount 和 pendingPassiveHookEffectsMount 這個(gè)兩個(gè)隊(duì)列中
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);
}
}
推入卸載隊(duì)列
這里是推入的邏輯,只展示推入掛載隊(duì)列的方法,推入卸載隊(duì)列是一樣的
function enqueuePendingPassiveHookEffectMount(fiber, effect) {
pendingPassiveHookEffectsMount.push(effect, fiber);
if (!rootDoesHavePassiveEffects) {
rootDoesHavePassiveEffects = true;
scheduleCallback(NormalPriority$1, function() {
flushPassiveEffects();
return null;
});
}
}
invokePassiveEffectCreate 執(zhí)行
之后又是一大推調(diào)度,協(xié)調(diào)的邏輯,等待協(xié)調(diào)執(zhí)行完畢后,之后會進(jìn)入 flushPassiveEffectsImpl ,函數(shù)太長了,只貼出相關(guān)的部分,邏輯是循環(huán)掛載 effect 隊(duì)列中的每一個(gè) effect 傳入到 invokePassiveEffectCreate 執(zhí)行
// ...
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();
}
}
// ...
這個(gè)函數(shù)會獲取 create 并執(zhí)行,然后將執(zhí)行結(jié)果掛載到 destroy 上,這里的 create 就是 useEffect 中的第一個(gè)參數(shù),從這里可以看出,如果有返回值,那么 destroy 就是第一個(gè)函數(shù)的返回值,沒有就是 undefined
function invokePassiveEffectCreate(effect) {
var create = effect.create;
effect.destroy = create();
}
卸載的時(shí)候會通過函數(shù)組件對應(yīng)的 fiber 獲取 effect 鏈表,然后遍歷鏈表,獲取環(huán)鏈上的每一個(gè)節(jié)點(diǎn),如果 destroy 不是 undefined 就執(zhí)行,所以如果 useEffect 第一個(gè)參數(shù)傳入 async, 那么這里的 destroy 就是一個(gè) promise 對象,對象是不能執(zhí)行的,所以報(bào)錯(cuò)。
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);
}
}
既然知道了原因那么,解決方案就非常簡單,直接手寫一個(gè)自定義 hook,包裹一下就可以處理這個(gè)問題了,hook 實(shí)現(xiàn)如下。
hook 實(shí)現(xiàn)
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>
)
}
這里其實(shí)有問題,因?yàn)榉祷刂涤肋h(yuǎn)是undefined,你可以開動(dòng)腦筋嘗試修復(fù)一下。
原文鏈接:https://blog.csdn.net/qq_33988065/article/details/115554178
相關(guān)推薦
- 2022-07-26 在Pycharm set ops_config=local之后,直接echo %ops_config
- 2023-01-12 C#11新特性之file關(guān)鍵字的用法教程_C#教程
- 2022-08-06 QT生成隨機(jī)驗(yàn)證碼的方法_C 語言
- 2022-05-17 解決使用maven打jar包缺失依賴包問題
- 2023-04-08 React中useCallback?useMemo到底該怎么用_React
- 2022-03-24 Android實(shí)現(xiàn)旋轉(zhuǎn)動(dòng)畫_Android
- 2022-08-15 SpringMVC異常處理流程總結(jié)
- 2022-10-03 Docker啟動(dòng)失敗報(bào)錯(cuò)Failed?to?start?Docker?Application?Con
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支