useEffect中不能使用async原理詳解
引言
當(dāng)我們嘗試在 useEffect 使用 async 的時(shí)候會(huì)報(bào)錯(cuò),但是一直沒(méi)有了解為什么,最近在看源碼,嘗試從源碼角度解釋報(bào)錯(cuò)的原因。
具體代碼分析
執(zhí)行 mountEffect
當(dāng)頁(yè)面中使用 useEffect 的時(shí)候,會(huì)在初始化的時(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 中會(huì)創(chuàng)建一個(gè) effect 節(jié)點(diǎn),然后添加到當(dāng)前函數(shù)對(duì)應(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ù)組件對(duì)應(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í)行完畢后,之后會(huì)進(jìn)入 flushPassiveEffectsImpl ,函數(shù)太長(zhǎng)了,只貼出相關(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ù)會(huì)獲取 create 并執(zhí)行,然后將執(zhí)行結(jié)果掛載到 destroy 上,這里的 create 就是 useEffect 中的第一個(gè)參數(shù),從這里可以看出,如果有返回值,那么 destroy 就是第一個(gè)函數(shù)的返回值,沒(méi)有就是 undefined
function invokePassiveEffectCreate(effect) { var create = effect.create; effect.destroy = create(); }
卸載的時(shí)候會(huì)通過(guò)函數(shù)組件對(duì)應(yīng)的 fiber 獲取 effect 鏈表,然后遍歷鏈表,獲取環(huán)鏈上的每一個(gè)節(jié)點(diǎn),如果 destroy 不是 undefined 就執(zhí)行,所以如果 useEffect 第一個(gè)參數(shù)傳入 async, 那么這里的 destroy 就是一個(gè) promise 對(duì)象,對(duì)象是不能執(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); } }
既然知道了原因那么,解決方案就非常簡(jiǎn)單,直接手寫一個(gè)自定義 hook,包裹一下就可以處理這個(gè)問(wèn)題了,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í)有問(wèn)題,因?yàn)榉祷刂涤肋h(yuǎn)是undefined,你可以開動(dòng)腦筋嘗試修復(fù)一下。
以上就是對(duì) useEffect 中為啥不能使用 async 的簡(jiǎn)單分析,有不足之處,歡迎提出,更多關(guān)于useEffect不能使用async的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
解讀useState第二個(gè)參數(shù)的"第二個(gè)參數(shù)"
這篇文章主要介紹了useState第二個(gè)參數(shù)的"第二個(gè)參數(shù)",具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03基于PixiJS實(shí)現(xiàn)react圖標(biāo)旋轉(zhuǎn)動(dòng)效
PixiJS是一個(gè)開源的基于web的渲染系統(tǒng),為游戲、數(shù)據(jù)可視化和其他圖形密集型項(xiàng)目提供了極快的性能,這篇文章主要介紹了用PixiJS實(shí)現(xiàn)react圖標(biāo)旋轉(zhuǎn)動(dòng)效,需要的朋友可以參考下2022-05-05React的特征單向數(shù)據(jù)流學(xué)習(xí)
這篇文章主要為大家介紹了React的特征單向數(shù)據(jù)流學(xué)習(xí),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09React函數(shù)組件和類組件的區(qū)別及說(shuō)明
這篇文章主要介紹了React函數(shù)組件和類組件的區(qū)別及說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08React實(shí)現(xiàn)復(fù)雜搜索表單的展開收起功能
本節(jié)對(duì)于需要展開收起效果的查詢表單進(jìn)行概述,主要涉及前端樣式知識(shí)。對(duì)React實(shí)現(xiàn)復(fù)雜搜索表單的展開-收起功能感興趣的朋友一起看看吧2021-09-09