React?Fiber原理深入分析
react16 版本之后引入了 fiber,整個(gè)架構(gòu)層面的 調(diào)度、協(xié)調(diào)、diff 算法以及渲染等都與 fiber 密切相關(guān)。所以為了更好地講解后面的內(nèi)容,需要對(duì) fiber 有個(gè)比較清晰的認(rèn)知。本章將介紹以下內(nèi)容:
為什么需要 fiberfiber 節(jié)點(diǎn)結(jié)構(gòu)中的屬性fiber 樹是如何構(gòu)建與更新的
為什么需要 fiber
Lin Clark 在 React Conf 2017 的演講中,他通過漫畫的形式,很好地講述了 fiber 為何出現(xiàn),下面我根據(jù)她的演講,結(jié)合我自己的理解來談一談 fiber 出現(xiàn)的原因。
fiber 之前
在 react15 及之前 fiber 未出現(xiàn)時(shí),react 的一系列執(zhí)行過程例如生命周期執(zhí)行、虛擬 dom 的比較、dom 樹的更新等都是同步的,一旦開始執(zhí)行就不會(huì)中斷,直到所有的工作流程全部結(jié)束為止。
要知道,react 所有的狀態(tài)更新,都是從根組件開始的,當(dāng)應(yīng)用組件樹比較龐大時(shí),一旦狀態(tài)開始變更,組件樹層層遞歸開始更新,js 主線程就不得不停止其他工作。例如組件樹一共有 1000 個(gè)組件需要更新,每個(gè)組件更新所需要的時(shí)間為 1s,那么在這 1s 內(nèi)瀏覽器都無法做其他的事情,用戶的點(diǎn)擊輸入等交互事件、頁面動(dòng)畫等都不會(huì)得到響應(yīng),體驗(yàn)就會(huì)非常的差。
這種情況下,函數(shù)堆棧的調(diào)用就像下圖一樣,層級(jí)很深,很長(zhǎng)時(shí)間不會(huì)返回
fiber 之后
為了解決這一問題,react 引入了 fiber 這種數(shù)據(jù)結(jié)構(gòu),將更新渲染耗時(shí)長(zhǎng)的大任務(wù),分為許多的小片。每個(gè)小片的任務(wù)執(zhí)行完成后,都先去執(zhí)行其他高優(yōu)先級(jí)的任務(wù)(例如用戶點(diǎn)擊輸入事件、動(dòng)畫等),這樣 js 的主線程就不會(huì)被 react 獨(dú)占,雖然任務(wù)執(zhí)行的總時(shí)間不變,但是頁面能夠及時(shí)響應(yīng)高優(yōu)先級(jí)任務(wù),顯得不會(huì)卡頓了。
fiber 分片模式下,瀏覽器主線程能夠定期被釋放,保證了渲染的幀率,函數(shù)的堆棧調(diào)用如下(波谷表示執(zhí)行分片任務(wù),波峰表示執(zhí)行其他高優(yōu)先級(jí)任務(wù)):
react 通過 fiber,為我們提供了一種跟蹤、調(diào)度、暫停和中止工作的便捷方式,保證了頁面的性能和流暢度。
fiber 節(jié)點(diǎn)結(jié)構(gòu)
fiber 是一種數(shù)據(jù)結(jié)構(gòu),每個(gè) fiber 節(jié)點(diǎn)的內(nèi)部,都保存了 dom 相關(guān)信息、fiber 樹相關(guān)的引用、要更新時(shí)的副作用等,我們可以看一下源碼中的 fiber 結(jié)構(gòu):
// packages/react-reconciler/src/ReactInternalTypes.js export type Fiber = {| // 作為靜態(tài)數(shù)據(jù)結(jié)構(gòu),存儲(chǔ)節(jié)點(diǎn) dom 相關(guān)信息 tag: WorkTag, // 組件的類型,取決于 react 的元素類型 key: null | string, elementType: any, // 元素類型 type: any, // 定義與此fiber關(guān)聯(lián)的功能或類。對(duì)于組件,它指向構(gòu)造函數(shù);對(duì)于DOM元素,它指定HTML tag stateNode: any, // 真實(shí) dom 節(jié)點(diǎn) // fiber 鏈表樹相關(guān) return: Fiber | null, // 父 fiber child: Fiber | null, // 第一個(gè)子 fiber sibling: Fiber | null, // 下一個(gè)兄弟 fiber index: number, // 在父 fiber 下面的子 fiber 中的下標(biāo) ref: | null | (((handle: mixed) => void) & {_stringRef: ?string, ...}) | RefObject, // 工作單元,用于計(jì)算 state 和 props 渲染 pendingProps: any, // 本次渲染需要使用的 props memoizedProps: any, // 上次渲染使用的 props updateQueue: mixed, // 用于狀態(tài)更新、回調(diào)函數(shù)、DOM更新的隊(duì)列 memoizedState: any, // 上次渲染后的 state 狀態(tài) dependencies: Dependencies | null, // contexts、events 等依賴 mode: TypeOfMode, // 副作用相關(guān) flags: Flags, // 記錄更新時(shí)當(dāng)前 fiber 的副作用(刪除、更新、替換等)狀態(tài) subtreeFlags: Flags, // 當(dāng)前子樹的副作用狀態(tài) deletions: Array<Fiber> | null, // 要?jiǎng)h除的子 fiber nextEffect: Fiber | null, // 下一個(gè)有副作用的 fiber firstEffect: Fiber | null, // 指向第一個(gè)有副作用的 fiber lastEffect: Fiber | null, // 指向最后一個(gè)有副作用的 fiber // 優(yōu)先級(jí)相關(guān) lanes: Lanes, childLanes: Lanes, alternate: Fiber | null, // 指向 workInProgress fiber 樹中對(duì)應(yīng)的節(jié)點(diǎn) actualDuration?: number, actualStartTime?: number, selfBaseDuration?: number, treeBaseDuration?: number, _debugID?: number, _debugSource?: Source | null, _debugOwner?: Fiber | null, _debugIsCurrentlyTiming?: boolean, _debugNeedsRemount?: boolean, _debugHookTypes?: Array<HookType> | null, |};
dom 相關(guān)屬性
fiber 中和 dom 節(jié)點(diǎn)相關(guān)的信息主要關(guān)注 tag
、key
、type
和 stateNode
。
tag
fiber 中 tag
屬性的 ts 類型為 workType,用于標(biāo)記不同的 react 組件類型,我們可以看一下源碼中 workType 的枚舉值:
// packages/react-reconciler/src/ReactWorkTags.js export const FunctionComponent = 0; export const ClassComponent = 1; export const IndeterminateComponent = 2; // Before we know whether it is function or class export const HostRoot = 3; // Root of a host tree. Could be nested inside another node. export const HostPortal = 4; // A subtree. Could be an entry point to a different renderer. export const HostComponent = 5; export const HostText = 6; export const Fragment = 7; export const Mode = 8; export const ContextConsumer = 9; export const ContextProvider = 10; export const ForwardRef = 11; export const Profiler = 12; export const SuspenseComponent = 13; export const MemoComponent = 14; export const SimpleMemoComponent = 15; export const LazyComponent = 16; export const IncompleteClassComponent = 17; export const DehydratedFragment = 18; export const SuspenseListComponent = 19; export const FundamentalComponent = 20; export const ScopeComponent = 21; export const Block = 22; export const OffscreenComponent = 23; export const LegacyHiddenComponent = 24;
在 react 協(xié)調(diào)時(shí),beginWork 和 completeWork 等流程時(shí),都會(huì)根據(jù) tag
類型的不同,去執(zhí)行不同的函數(shù)處理 fiber 節(jié)點(diǎn)。
key 和 type
key
和 type
兩項(xiàng)用于 react diff 過程中確定 fiber 是否可以復(fù)用。
key
為用戶定義的唯一值。type
定義與此fiber關(guān)聯(lián)的功能或類。對(duì)于組件,它指向函數(shù)或者類本身;對(duì)于DOM元素,它指定HTML tag。
stateNode
stateNode
用于記錄當(dāng)前fiber所對(duì)應(yīng)的真實(shí)dom節(jié)點(diǎn)或者當(dāng)前虛擬組件的實(shí)例,這么做的原因第一是為了實(shí)現(xiàn)Ref
,第二是為了實(shí)現(xiàn)真實(shí) dom的跟蹤。
鏈表樹相關(guān)屬性
我們看一下和 fiber 鏈表樹構(gòu)建相關(guān)的 return
、child
和 sibling
幾個(gè)字段:
- return:指向父 fiber,若沒有父 fiber 則為 null
- child: 指向第一個(gè)子 fiber,若沒有任何子 fiber 則為 null
- sibling:指向下一個(gè)兄弟 fiber,若沒有下一個(gè)兄弟 fiber 則為 null
通過這幾個(gè)字段,各個(gè) fiber 節(jié)點(diǎn)構(gòu)成了 fiber 鏈表樹結(jié)構(gòu):
副作用相關(guān)屬性
首先理解一下 react 中的副作用,舉一個(gè)生活中比較通俗的例子:我們感冒了本來吃點(diǎn)藥就沒事了,但是吃了藥發(fā)現(xiàn)身體過敏了,而這個(gè)“過敏”就是副作用。react 中,我們修改了 state、props、ref 等數(shù)據(jù),除了數(shù)據(jù)改變之外,還會(huì)引起 dom 的變化,這種 render 階段不能完成的工作,我們稱之為副作用。相關(guān)參考講解:進(jìn)入學(xué)習(xí)
flags
react 中通過 flags 記錄每個(gè)節(jié)點(diǎn)diff后需要變更的狀態(tài),例如 dom 的添加、替換、刪除等等。我們可以看一下源碼中 Flags 枚舉類型:
例如 Deletion
代表更新時(shí)要對(duì) dom 進(jìn)行刪除,Placement
代表要進(jìn)行添加或者替換等等。
// packages/react-reconciler/src/ReactFiberFlags.js export type Flags = number; export const NoFlags = /* */ 0b000000000000000000; export const PerformedWork = /* */ 0b000000000000000001; export const Placement = /* */ 0b000000000000000010; export const Update = /* */ 0b000000000000000100; export const PlacementAndUpdate = /* */ 0b000000000000000110; export const Deletion = /* */ 0b000000000000001000; export const ContentReset = /* */ 0b000000000000010000; export const Callback = /* */ 0b000000000000100000; export const DidCapture = /* */ 0b000000000001000000; export const Ref = /* */ 0b000000000010000000; export const Snapshot = /* */ 0b000000000100000000; export const Passive = /* */ 0b000000001000000000; export const PassiveUnmountPendingDev = /* */ 0b000010000000000000; export const Hydrating = /* */ 0b000000010000000000; export const HydratingAndUpdate = /* */ 0b000000010000000100; export const LifecycleEffectMask = /* */ 0b000000001110100100; export const HostEffectMask = /* */ 0b000000011111111111; export const Incomplete = /* */ 0b000000100000000000; export const ShouldCapture = /* */ 0b000001000000000000; export const ForceUpdateForLegacySuspense = /* */ 0b000100000000000000; export const PassiveStatic = /* */ 0b001000000000000000; export const BeforeMutationMask = /* */ 0b000000001100001010; export const MutationMask = /* */ 0b000000010010011110; export const LayoutMask = /* */ 0b000000000010100100; export const PassiveMask = /* */ 0b000000001000001000; export const StaticMask = /* */ 0b001000000000000000; export const MountLayoutDev = /* */ 0b010000000000000000; export const MountPassiveDev = /* */ 0b100000000000000000;
Effect List
在 render 階段時(shí),react 會(huì)采用深度優(yōu)先遍歷,對(duì) fiber 樹進(jìn)行遍歷,把每一個(gè)有副作用的 fiber 篩選出來,最后構(gòu)建生成一個(gè)只帶副作用的 Effect list 鏈表。和該鏈表相關(guān)的字段有 firstEffect
、nextEffect
和 lastEffect
:
firstEffect
指向第一個(gè)有副作用的 fiber 節(jié)點(diǎn),lastEffect
指向最后一個(gè)有副作用的節(jié)點(diǎn),中間的節(jié)點(diǎn)全部通過 nextEffect
鏈接,最終形成 Effect 鏈表。
在 commit 階段,React 拿到 Effect list 鏈表中的數(shù)據(jù)后,根據(jù)每一個(gè) fiber 節(jié)點(diǎn)的 flags 類型,對(duì)相應(yīng)的 DOM 進(jìn)行更改。
其他
其他需要重點(diǎn)關(guān)注一下的屬性還有 lane
和 alternate
。
lane
lane
代表 react 要執(zhí)行的 fiber 任務(wù)的優(yōu)先級(jí),通過這個(gè)字段,render 階段 react 確定應(yīng)該優(yōu)先將哪些任務(wù)提交到 commit 階段去執(zhí)行。
我們看一下源碼中 lane
的枚舉值:
// packages/react-reconciler/src/ReactFiberLane.js InputDiscreteHydrationLane: Lane = /* */ 0b0000000000000000000000000000100; const InputDiscreteLanes: Lanes = /* */ 0b0000000000000000000000000011000; const InputContinuousHydrationLane: Lane = /* */ 0b0000000000000000000000000100000; const InputContinuousLanes: Lanes = /* */ 0b0000000000000000000000011000000; export const DefaultHydrationLane: Lane = /* */ 0b0000000000000000000000100000000; export const DefaultLanes: Lanes = /* */ 0b0000000000000000000111000000000; const TransitionHydrationLane: Lane = /* */ 0b0000000000000000001000000000000; const TransitionLanes: Lanes = /* */ 0b0000000001111111110000000000000; const RetryLanes: Lanes = /* */ 0b0000011110000000000000000000000; export const SomeRetryLane: Lanes = /* */ 0b0000010000000000000000000000000; export const SelectiveHydrationLane: Lane = /* */ 0b0000100000000000000000000000000; const NonIdleLanes = /* */ 0b0000111111111111111111111111111; export const IdleHydrationLane: Lane = /* */ 0b0001000000000000000000000000000; const IdleLanes: Lanes = /* */ 0b0110000000000000000000000000000; export const OffscreenLane: Lane = /* */ 0b1000000000000000000000000000000;
同 Flags 的枚舉值一樣,Lanes 也是用 31 位的二進(jìn)制數(shù)表示,表示了 31 條賽道,位數(shù)越小的賽道,代表的優(yōu)先級(jí)越高。
例如 InputDiscreteHydrationLane
、InputDiscreteLanes
、InputContinuousHydrationLane
等用戶交互引起的更新的優(yōu)先級(jí)較高,DefaultLanes
這種請(qǐng)求數(shù)據(jù)引起更新的優(yōu)先級(jí)中等,而 OffscreenLane
、IdleLanes
這種優(yōu)先級(jí)較低。
優(yōu)先級(jí)越低的任務(wù),在 render 階段越容易被打斷,commit 執(zhí)行的時(shí)機(jī)越靠后。
alternate
當(dāng) react 的狀態(tài)發(fā)生更新時(shí),當(dāng)前頁面所對(duì)應(yīng)的 fiber 樹稱為 current Fiber,同時(shí) react 會(huì)根據(jù)新的狀態(tài)構(gòu)建一顆新的 fiber 樹,稱為 workInProgress Fiber。current Fiber 中每個(gè) fiber 節(jié)點(diǎn)通過 alternate
字段,指向 workInProgress Fiber 中對(duì)應(yīng)的 fiber 節(jié)點(diǎn)。同樣 workInProgress Fiber 中的 fiber
節(jié)點(diǎn)的 alternate
字段也會(huì)指向 current Fiber 中對(duì)應(yīng)的 fiber 節(jié)點(diǎn)。
fiber 樹的構(gòu)建與更新
下面我們結(jié)合源碼,來看一下實(shí)際工作過程中 fiber 樹的構(gòu)建與更新過程。
mount 過程
react 首次 mount 開始執(zhí)行時(shí),以 ReactDOM.render
為入口函數(shù),會(huì)經(jīng)過如下一系列的函數(shù)調(diào)用:ReactDOM.render
——> legacyRenderSubtreeIntoContainer
——> legacyCreateRootFromDOMContainer
——> createLegacyRoot
——> ReactDOMBlockingRoot
——> ReactDOMRoot
——> createRootImpl
——> createContainer
——> createFiberRoot
——> createHostRootFiber
——> createFiber
在 createFiber
函數(shù)中,調(diào)用 FiberNode
構(gòu)造函數(shù),創(chuàng)建了 rootFiber,它是 react 應(yīng)用的根 fiber:
// packages/react-reconciler/src/ReactFiber.old.js const createFiber = function( tag: WorkTag, pendingProps: mixed, key: null | string, mode: TypeOfMode, ): Fiber { return new FiberNode(tag, pendingProps, key, mode); };
在 createFiberRoot
函數(shù)中,調(diào)用 FiberRootNode
構(gòu)造函數(shù),創(chuàng)建了 fiberRoot,它指向真實(shí)根 dom 節(jié)點(diǎn)。
// packages/react-reconciler/src/ReactFiberRoot.old.js export function createFiberRoot( containerInfo: any, tag: RootTag, hydrate: boolean, hydrationCallbacks: null | SuspenseHydrationCallbacks, ): FiberRoot { const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any); if (enableSuspenseCallback) { root.hydrationCallbacks = hydrationCallbacks; } const uninitializedFiber = createHostRootFiber(tag); root.current = uninitializedFiber; uninitializedFiber.stateNode = root; initializeUpdateQueue(uninitializedFiber); return root; }
另外 createFiberRoot
函數(shù)中,還讓 rootFiber 的 stateNode
字段指向了 fiberRoot,fiberRoot 的 current
字段指向了 rootFiber。從而一顆最原始的 fiber 樹根節(jié)點(diǎn)就創(chuàng)建完成了:
上面的 rootFiber 和 fiberRoot 創(chuàng)建完成后,react 就會(huì)根據(jù) jsx 的內(nèi)容去創(chuàng)建詳細(xì)的 dom 樹了,例如有如下的 jsx:
<div id="root"> <div id="a1"> <div id="b1"> <div id="c1"> <div id="d1"></div> <div id="d2"></div> <div id="d3"></div> </div> <div id="c2"></div> </div> </div> </div>
react 對(duì)于 fiber 結(jié)構(gòu)的創(chuàng)建和更新,都是采用深度優(yōu)先遍歷,從 rootFiber(此處對(duì)應(yīng)id為root的節(jié)點(diǎn))開始,首先創(chuàng)建 child a1,然后發(fā)現(xiàn) a1 有子節(jié)點(diǎn) b1,繼續(xù)對(duì) b1 進(jìn)行遍歷,b1 有子節(jié)點(diǎn) c1,再去創(chuàng)建 c1 的子節(jié)點(diǎn) d1、d2、d3,直至發(fā)現(xiàn) d1、d2、d3 都沒有子節(jié)點(diǎn)來了,再回去創(chuàng)建 c2.
上面的過程,每個(gè)節(jié)點(diǎn)開始創(chuàng)建時(shí),執(zhí)行 beginWork
流程,直至該節(jié)點(diǎn)的所有子孫節(jié)點(diǎn)都創(chuàng)建(更新)完成后,執(zhí)行 completeWork
流程,過程的圖示如下:
update 過程
update 時(shí),react 會(huì)根據(jù)新的 jsx 內(nèi)容創(chuàng)建新的 workInProgress fiber,還是通過深度優(yōu)先遍歷,對(duì)發(fā)生改變的 fiber 打上不同的 flags
副作用標(biāo)簽,并通過 firstEffect
、nextEffect
等字段形成 Effect List 鏈表。
例如上面的 jsx 結(jié)構(gòu),發(fā)生了如下的更新:
<div id="root"> <div id="a1"> <div id="b1"> <div id="c1"> <div id="d1"></div> - <div id="d2"></div> - <div id="d3"></div> </div> - <div id="c2"></div> + <div id="c2">new content</div> </div> </div> </div>
react 會(huì)根據(jù)新的 jsx 解析后的內(nèi)容,調(diào)用 createWorkInProgress
函數(shù)創(chuàng)建 workInProgress fiber,對(duì)其標(biāo)記副作用:
// packages/react-reconciler/src/ReactFiber.old.js export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber { let workInProgress = current.alternate; if (workInProgress === null) { // 區(qū)分 mount 還是 update workInProgress = createFiber( current.tag, pendingProps, current.key, current.mode, ); workInProgress.elementType = current.elementType; workInProgress.type = current.type; workInProgress.stateNode = current.stateNode; if (__DEV__) { workInProgress._debugID = current._debugID; workInProgress._debugSource = current._debugSource; workInProgress._debugOwner = current._debugOwner; workInProgress._debugHookTypes = current._debugHookTypes; } workInProgress.alternate = current; current.alternate = workInProgress; } else { workInProgress.pendingProps = pendingProps; workInProgress.type = current.type; workInProgress.subtreeFlags = NoFlags; workInProgress.deletions = null; if (enableProfilerTimer) { workInProgress.actualDuration = 0; workInProgress.actualStartTime = -1; } } // 重置所有的副作用 workInProgress.flags = current.flags & StaticMask; workInProgress.childLanes = current.childLanes; workInProgress.lanes = current.lanes; workInProgress.child = current.child; workInProgress.memoizedProps = current.memoizedProps; workInProgress.memoizedState = current.memoizedState; workInProgress.updateQueue = current.updateQueue; // 克隆依賴 const currentDependencies = current.dependencies; workInProgress.dependencies = currentDependencies === null ? null : { lanes: currentDependencies.lanes, firstContext: currentDependencies.firstContext, }; workInProgress.sibling = current.sibling; workInProgress.index = current.index; workInProgress.ref = current.ref; if (enableProfilerTimer) { workInProgress.selfBaseDuration = current.selfBaseDuration; workInProgress.treeBaseDuration = current.treeBaseDuration; } if (__DEV__) { workInProgress._debugNeedsRemount = current._debugNeedsRemount; switch (workInProgress.tag) { case IndeterminateComponent: case FunctionComponent: case SimpleMemoComponent: workInProgress.type = resolveFunctionForHotReloading(current.type); break; case ClassComponent: workInProgress.type = resolveClassForHotReloading(current.type); break; case ForwardRef: workInProgress.type = resolveForwardRefForHotReloading(current.type); break; default: break; } } return workInProgress; }
最終生成的 workInProgress fiber 圖示如下:
然后如上面所說,current fiber 和 workInProgress fiber 中對(duì)應(yīng)的 alternate 會(huì)相互指向,然后 workInProgress fiber 完全創(chuàng)建完成后,fiberRoot 的 current
字段的指向會(huì)從 current fiber 中的 rootFiber 改為 workInProgress fiber 中的 rootFiber:
總結(jié)
本章講解了 fiber 出現(xiàn)的主要原因、fiber 節(jié)點(diǎn)中主要的屬性以及 fiber 樹是如何構(gòu)建與更新的。
到此這篇關(guān)于React Fiber原理深入分析的文章就介紹到這了,更多相關(guān)React Fiber內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
React?useEffect異步操作常見問題小結(jié)
本文主要介紹了React?useEffect異步操作常見問題小結(jié),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06React特征Form?單向數(shù)據(jù)流示例詳解
這篇文章主要為大家介紹了React特征Form?單向數(shù)據(jù)流示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09React性能優(yōu)化系列之減少props改變的實(shí)現(xiàn)方法
這篇文章主要介紹了React性能優(yōu)化系列之減少props改變的實(shí)現(xiàn)方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2019-01-01React forwardRef的使用方法及注意點(diǎn)
React.forwardRef的API中ref必須指向dom元素而不是React組件,通過一段示例代碼給大家介紹了React forwardRef使用方法及注意點(diǎn)還有一些特殊情況分析,感興趣的朋友跟隨小編一起看看吧2021-06-06詳解使用webpack+electron+reactJs開發(fā)windows桌面應(yīng)用
這篇文章主要介紹了詳解使用webpack+electron+reactJs開發(fā)windows桌面應(yīng)用,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2019-02-02react-redux多個(gè)組件數(shù)據(jù)共享的方法
這篇文章主要介紹了react-redux多個(gè)組件數(shù)據(jù)共享的方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-08-08axios請(qǐng)求響應(yīng)數(shù)據(jù)加解密封裝實(shí)現(xiàn)詳解
這篇文章主要為大家介紹了axios請(qǐng)求響應(yīng)數(shù)據(jù)加解密封裝實(shí)現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03React實(shí)現(xiàn)動(dòng)效彈窗組件
最近在使用react開發(fā)項(xiàng)目,遇到這樣一個(gè)需求實(shí)現(xiàn)一個(gè)帶有動(dòng)效的 React 彈窗組件,如果不考慮動(dòng)效,很容易實(shí)現(xiàn),接下來小編通過本文給大家介紹React實(shí)現(xiàn)動(dòng)效彈窗組件的實(shí)現(xiàn)代碼,一起看看吧2021-06-06