React更新渲染原理深入分析
當(dāng)我們調(diào)用 setState 之后發(fā)生了什么?react經(jīng)歷了怎樣的過程將新的 state 渲染到頁面上?
一次react
更新,核心就是對虛擬dom
進行diff
,找出最少的需要變化的dom
節(jié)點,然后對其進行相應(yīng)的dom
操作,用戶即可在頁面上看到更新。但 react 作為廣泛使用的框架,需要考慮更多的因素,考慮多個更新的優(yōu)先級,考慮主線程占用時長,考慮diff
算法復(fù)雜度,考慮性能。。等等,本文就來探討一下react在其內(nèi)部是如何處理數(shù)據(jù)更新的。
react在內(nèi)部使用fiber
這種數(shù)據(jù)結(jié)構(gòu)來作為虛擬dom【react16+】,它與dom tree
一一對應(yīng),形成fiber tree
,一次react更新,本質(zhì)是fiber tree
結(jié)構(gòu)的更新變化。而fiber tree
結(jié)構(gòu)的更新,用更專業(yè)的術(shù)語來講,其實就是fiber tree
的協(xié)調(diào)(Reconcile)。Reconcile
中文意思是調(diào)和、使一致,協(xié)調(diào)fiber tree,就是調(diào)整fiber tree
的結(jié)構(gòu),使其和更新后的jsx
模版結(jié)構(gòu)、dom tree
保持一致。
react從16起,將更新機制分為三個模塊,也可以說是三個步驟,分別是Schedule
【調(diào)度】、Reconcile
【協(xié)調(diào)】、render
【渲染】
Schedule
為什么需要Schedule?
首先我們要知道react在進行協(xié)調(diào)時,提供了兩種模式:Legacy mode
同步阻塞模式和 Concurrent mode
并行模式。
不同上下文中的更新會觸發(fā)不同的模式,如果是在 event
、setTimeout
、network request
的 callback
中觸發(fā)更新,react 會采用 Legacy
模式。如果更新與 Suspense
、useTransition
、OffScreen
相關(guān),那么 react 會采用 Concurrent
模式。
Legacy mode
Legacy mode
在協(xié)調(diào)時會啟動 workLoopSync
。workLoopSync
開始工作以后,要等到所有 fiber node
都處理完畢以后,才會結(jié)束工作,也就是 fiber tree
的協(xié)調(diào)過程不可中斷。
Legacy mode
存在的問題:如果 fiber tree
的結(jié)構(gòu)很復(fù)雜,那么協(xié)調(diào) fiber tree
可能會占用大量的時間,導(dǎo)致主線程會一直被 js 引擎占用,渲染引擎無法在規(guī)定時間(瀏覽器刷新頻率 - 16.7ms)內(nèi)完成工作,使得頁面出現(xiàn)卡頓(掉幀),影響用戶體驗。
Concurrent mode
鑒于Legacy mode
存在的問題,react團隊在react 16
中提出了 Concurrent mode
的概念,并在react 18
中開放使用。react16、17一直為此做準(zhǔn)備。
Concurrent
模式最大的意義在于,使用Concurrent
模式以后的react的應(yīng)用可以做到:
- 協(xié)調(diào)可以中斷、恢復(fù);不會長時間阻塞瀏覽器渲染
- 高優(yōu)先級更新可以中斷低優(yōu)先級更新,優(yōu)先渲染
那么,怎么做到這兩點呢?
事實上,Schedule
就是用來完成這個任務(wù)的,調(diào)度任務(wù)的優(yōu)先級,使高優(yōu)先級任務(wù)優(yōu)先進入Reconcile
,并且提供中斷和恢復(fù)機制。
時間切片
react
采用時間切片的方式來實現(xiàn)協(xié)調(diào)的中斷和恢復(fù),Concurrent mode
在協(xié)調(diào)時會啟動 workLoopConcurrent
。 workLoopConcurrent
開始工作以后,每次協(xié)調(diào) fiber node
時,都會判斷當(dāng)前時間片是否到期。如果時間片到期,會停止當(dāng)前 workLoopConcurrent
,讓出主線程,然后請求下一個時間片繼續(xù)協(xié)調(diào)。
協(xié)調(diào)的中斷及恢復(fù),類似于瀏覽器的eventloop
,js引擎和渲染引擎互斥,在主線程中交替工作。
我們可以通過模擬 eventLoop
來實現(xiàn)時間分片以及重新請求時間片。一段 js 程序,如果在規(guī)定時間內(nèi)沒有結(jié)束,那我們可以主動結(jié)束它,然后請求一個新的時間片,在下一個時間片內(nèi)繼續(xù)處理上一次沒有結(jié)束的任務(wù)。
let taskQueue = []; // 任務(wù)列表 let shouldTimeEnd = 5ms; // 一個時間片定義為 5ms let channel = new MessageChannel(); // 創(chuàng)建一個 MessageChannel 實例 function wookLoop() { let beginTime = performance.now(); // 記錄開始時間 while(true) { // 循環(huán)處理 taskQueue 中的任務(wù) let currentTime = performance.now(); // 記錄下一個任務(wù)開始時的時間 if (currentTime - beginTime >= shouldTimeEnd) break; // 時間片已經(jīng)到期,結(jié)束任務(wù)處理 processTask(); // 時間片沒有到期,繼續(xù)處理任務(wù) } if (taskQueue.length) { // 時間片到期,通過調(diào)用 postMessage,請求下一個時間片 channel.port2.postMessage(null); } } channel.port1.onmessage = wookLoop; // 在下一個時間片內(nèi)繼續(xù)處理任務(wù) workLoop();
和瀏覽器的消息隊列 一樣, react
也會維護一個任務(wù)隊列 taskQueue
,然后通過 workLoop
遍歷 taskQueue
,依次處理 taskQueue
中的任務(wù)。
taskQueue
中收集任務(wù)是有先后處理順序的,workLoop
每次處理 taskQueue
中的任務(wù)時,都會挑選優(yōu)先級最高的任務(wù)進行處理。
每觸發(fā)一次 react
更新,意味著一次 fiber tree
的協(xié)調(diào),但協(xié)調(diào)并不會在更新觸發(fā)時立刻同步進行。相反,react 會為這一次更新,生成一個 task
,并添加到 taskQueue
中,fiber tree
的協(xié)調(diào)方法會作為新建 task
的 callback
。當(dāng) wookLoop
開始處理該 task
時,才會觸發(fā) task
的 callback
,開始 fiber tree
的協(xié)調(diào)。
任務(wù)的優(yōu)先級
react在內(nèi)部定義了 5 種類型的優(yōu)先級,以及對應(yīng)的超時時間timeout
ImmediatePriority
, 直接優(yōu)先級,對應(yīng)用戶的click
、input
、focus
等操作;timeout
為 -1,表示任務(wù)要盡快處理;UserBlockingPriority
,用戶阻塞優(yōu)先級,對應(yīng)用戶的mousemove
、scroll
等操作;timeout
為 250 ms;NormalPriority
,普通優(yōu)先級,對應(yīng)網(wǎng)絡(luò)請求、useTransition
等操作;timeout
為 5000 ms;LowPriority
,低優(yōu)先級(未找到應(yīng)用場景);timeout
為 10000 ms;IdlePriority
,空閑優(yōu)先級,如OffScreen
;timeout
為 1073741823 ms;
5 種優(yōu)先級的順序為: ImmediatePriority
> UserBlockingPriority
> NormalPriority
> LowPriority
> IdlePriority
。
在確定了任務(wù)的優(yōu)先級以后,react 會根據(jù)優(yōu)先級為任務(wù)計算一個過期時間 expirationTime
,即 expirationTime
= currentTime
+ timeout
,然后根據(jù) expirationTime
時間來決定任務(wù)處理的先后順序。
expirationTime
越小的任務(wù)會被排在task
隊列的越前面,之所以需要timeout
,而不是直接對比優(yōu)先級等級,是為了避免低優(yōu)先級任務(wù)長時間被 插隊而導(dǎo)致一直無響應(yīng);同時,在時間分片到期時,需要根據(jù)expirationTime
判斷下一個要處理的任務(wù)是否過期,如果已過期,就不能讓出主線程,需要立即處理。
??注:react17中用Lanes重構(gòu)了優(yōu)先級算法,此處不展開陳述,有興趣的同學(xué)可查閱相關(guān)文檔。
獲取最先處理的task
react 采用了小頂堆來存儲task
,實現(xiàn)最小優(yōu)先隊列,即 taskQueue
是一個小頂堆,放在堆頂?shù)?code>task是需要最先處理的。
使用最小堆時,有三個操作:push
、pop
、peek
。
push
,入堆操作,即將 task
添加到 taskQueue
中。添加一個新創(chuàng)建的 task
時,會將 task
添加到最小堆的堆底,然后對最小堆做自底向上的調(diào)整。調(diào)整時,會比較堆節(jié)點(task
) 的 expirationTime
,將 expirationTime
較小的 task
向上調(diào)整。* peek
,獲取堆頂元素,即獲取需要最先處理的 task
,執(zhí)行 task
的 callback
,開始 fiber tree
的協(xié)調(diào)。* pop
,堆頂元素出堆,即 task
處理完畢,從 taskQueue
中移除。移除堆頂元素以后,會對最小堆做自頂向下的調(diào)整。調(diào)整時,也是比較堆節(jié)點(task
) 的 expirationTime
,將 expirationTime
較大的 task
向下調(diào)整。### 高優(yōu)先級的更新中斷低優(yōu)先級的更新
Concurrent
模式下,如果在低優(yōu)先級更新的協(xié)調(diào)過程中,有高優(yōu)先級更新進來,那么高優(yōu)先級更新會中斷低優(yōu)先級更新的協(xié)調(diào)過程。
每次拿到新的時間片以后,workLoopConcurrent
都會判斷本次協(xié)調(diào)對應(yīng)的優(yōu)先級和上一次時間片到期中斷的協(xié)調(diào)的優(yōu)先級是否一樣。如果一樣,說明沒有更高優(yōu)先級的更新產(chǎn)生,可以繼續(xù)上次未完成的協(xié)調(diào);如果不一樣,說明有更高優(yōu)先級的更新進來,此時要清空之前已開始的協(xié)調(diào)過程,從根節(jié)點開始重新協(xié)調(diào)。等高優(yōu)先級更新處理完成以后,再次從根節(jié)點開始處理低優(yōu)先級更新。
Reconcile
前面說到,reconcile
(協(xié)調(diào))就是fiber tree
結(jié)構(gòu)的更新,那么具體是怎樣更新的呢?本小節(jié)就來解答這個問題。
前置知識
從jsx到dom
Step1: 從jsx
生成react element
:
jsx
模板通過 babel
編譯為 createElement
方法;執(zhí)行組件方法,觸發(fā) createElement
的執(zhí)行,返回 react element
;
Step2: 從react element
生成fiber tree
:
fiber tree
中存在三種類型的指針child
、sibling
、return
。其中,child
指向第一個子節(jié)點,sibling
指向兄弟節(jié)點,return
指針指向父節(jié)點;*fiber tree
采用的深度優(yōu)先遍歷,如果節(jié)點有子節(jié)點,先遍歷子節(jié)點;子節(jié)點遍歷結(jié)束以后,再遍歷兄弟節(jié)點;沒有子節(jié)點、兄弟節(jié)點,就返回父節(jié)點,遍歷父節(jié)點的兄弟節(jié)點;* 當(dāng)節(jié)點的return
指針返回null
時,fiber tree
的遍歷結(jié)束;Step3:fiber tree
生成之后,從fiber tree
到真實dom
,就是處理fiber tree
上對應(yīng)的副作用,包括:- 所有
dom
節(jié)點的新增; componentDidMount
、useEffect
的callback
函數(shù)的觸發(fā);ref
引用的初始化;
雙緩存fiber tree
react 做更新處理時,會同時存在兩顆 fiber tree
。一顆是已經(jīng)存在的 old fiber tree
,對應(yīng)當(dāng)前屏幕顯示的內(nèi)容,稱為 current fiber tree
;另外一顆是更新過程中構(gòu)建的 new fiber tree
,稱為 workInProgress fiber tree
。
current fiber tree
和workInProgress fiber tree
可以通過alternate
指針互相訪問
當(dāng)更新完成以后,使用 workInProgress fiber tree
替換掉 current fiber tree
,作為下一次更新的 current fiber tree
。
協(xié)調(diào)的過程
協(xié)調(diào)過程中主要做三件事情:
1.為 workInProgress fiber tree
生成 fiber node
;
2.為發(fā)生變化的 fiber node
標(biāo)記副作用 effect
;
3.收集帶 effect
的 fiber node
;
生成workInProgress fiber tree
workInProgress fiber tree
作為一顆新樹,生成 fiber node
的方式有三種:
- 克隆(淺拷貝)
current fiber node
,意味著原來的dom
節(jié)點可以復(fù)用,只需要更新dom
節(jié)點的屬性,或者移動dom
節(jié)點; - 新建一個
fiber node
,意味著需要新增加一個dom
節(jié)點; - 直接復(fù)用
current fiber node
,表示對應(yīng)的dom
節(jié)點完全不用做任何處理;
復(fù)用的場景:當(dāng)子組件的渲染方法(類組件的 render
、函數(shù)組件方法)沒有觸發(fā),(比如使用了React.memo
),沒有返回新的 react element
,子節(jié)點就可以直接復(fù)用 current fiber node
。
在日常開發(fā)過程中,我們可以通過合理使用 ShouldComponentUpdate
、React.memo
,阻止不必要的組件重新 render
,通過直接復(fù)用 current fiber node
,加快 workInProgress fiber tree
的協(xié)調(diào),達到優(yōu)化的目的。
相反,只要組件的渲染方法被觸發(fā),返回新的 react element
,那么就需要根據(jù)新的 react element
為子節(jié)點創(chuàng)建 fiber node
(通過淺拷貝或新建)。
- 如果能在
current fiber tree
中找到匹配節(jié)點,那么可以通過克隆(淺拷貝)current fiber node
的方式來創(chuàng)建新的節(jié)點; - 相反,如果無法在
current fiber tree
找到匹配節(jié)點,那么就需要重新創(chuàng)建一個新的節(jié)點;
我們常說的diff算法
就是發(fā)生在這一環(huán)節(jié)。
diff算法
比較的雙方是 workInProgress fiber tree
中用于構(gòu)建 fiber node
的 react element
和 current fiber tree
中的 fiber node
,比較兩者的 key
和 type
,根據(jù)比較結(jié)果來決定如何為 workInProgress fiber tree
創(chuàng)建 fiber node
。
【 key 和 type 】:
key
就是 jsx
模板中元素上的 key
屬性。如果不寫默認(rèn)為undefined
。jsx
模板轉(zhuǎn)化為 react element
后,元素的 key
屬性會作為 react element
的 key
屬性。同樣的,react element
轉(zhuǎn)化為 fiber node
以后,react element
的 key
屬性也會作為 fiber node
的 key
屬性。
jsx
中不同的元素類型,有不同的type
:
<Component name="xxxx" /> //type = Component, 是一個函數(shù) <div></div> // type = "div", 是一個字符串 <React.Fragment></React.Fragment> // type = React.Fragment, 是一個數(shù)字(react 內(nèi)部定義的);
jsx
模板轉(zhuǎn)化為 react element
以后,react element
的 type
屬性會根據(jù) jsx
元素的類型賦不同的值,可能是組件函數(shù),也可能是 dom
標(biāo)簽字符串,還可能是數(shù)字。 react element
轉(zhuǎn)化為 fiber node
以后,react element
的 type
屬性也會作為 fiber node
的 type
屬性。
綜上,判斷拷貝 current fiber node
的邏輯,概括來就是:
reactElement.key === currentFiberNode.key && reactElement.type === currentFiberNode.type, current fiber node //可以克??; reactElement.key !== currentFiberNode.key, current fiber node //不可克??; reactElement.key === currentFiberNode.key && reactElement.type !== currentFiberNode.type, current fiber node //不可克?。?
diff 算法:
- 已匹配的父節(jié)點的直接子節(jié)點進行比較,不跨父節(jié)點比較;
- 通過比較
key
、type
來判斷是否需要克隆current fiber node
。只有key
和type
都相等,才克隆current fiber node
作為新的節(jié)點,否則就需要新建一個節(jié)點。key
值和節(jié)點類型type
,key
的優(yōu)先級更高。如果key
值不相同,那么節(jié)點不可克隆。 - 當(dāng)比較
single react element
和current fiber node list
時,只需要遍歷current fiber node list
,比較每個current fiber node
和react element
的key
值和type
。只有key
和type
都相等,react element
和current fiber node
才能匹配。如果有匹配的,直接克隆current fiber node
,作為react element
對應(yīng)的workInProgress fiber node
。如果沒有匹配的current fiber node
,就需要為react element
重新創(chuàng)建一個新的fiber node
作為workInProgress fiber node
。 - 當(dāng)比較
react element list
和current fiber node list
時,還需要通過列表下標(biāo)index
判斷wokrInProgress fiber node
是否相對于克隆的current fiber node
發(fā)生了移動。這也是diff
中最復(fù)雜的地方。
為發(fā)生變化的fiber node標(biāo)記effect
判斷節(jié)點是否發(fā)生變化
- 節(jié)點只要是重新創(chuàng)建的而不是克隆自
current fiber node
,那么節(jié)點就百分之百發(fā)生了變化,需要更新;* 節(jié)點克隆自current fiber node
,需要比較props
是否發(fā)生了變化,如果props
發(fā)生了變化,節(jié)點需要更新;* 節(jié)點克隆自current fiber node
,且是組件類型,還需要比較state
是否發(fā)生了變化,如果state
發(fā)生了變化,節(jié)點需要更新;常見的effect
類型: Placement
,放置,只針對dom
類型的fiber node
,表示節(jié)點需要做移動或者添加操作。Update
,更新,針對所有類型的fiber node
,表示fiber node
需要做更新操作。PlacementAndUpdate
,放置并更新,只針對dom
類型的fiber node
,表示節(jié)點發(fā)生了移動且props
發(fā)生了變化。Ref
,表示節(jié)點存在ref
,需要初始化 / 更新ref.current
。Deletion
,刪除,針對所有類型的fiber node
,表示fiber node
需要移除。Snapshot
,快照,主要是針對類組件fiber node
。當(dāng)類組件fiber node
發(fā)生了mount
或者update
操作,且定義了getSnapshotBeforeUpdate
方法,就會標(biāo)記Snapshot
。Passive
,主要針對函數(shù)組件fiber node
,表示函數(shù)組件使用了useEffect
。當(dāng)函數(shù)組件節(jié)點發(fā)生mount
或者update
操作,且使用了useEffect hook
,就會給fiber node
標(biāo)記Passive
。Layout
,主要針對函數(shù)組件fiber node
,表示函數(shù)組件使用了useLayoutEffect
。當(dāng)函數(shù)組件節(jié)點發(fā)生mount
或者update
操作,且使用了useLayoutEffect hook
,就會給fiber node
標(biāo)記Layout
。
react 使用二進制數(shù)來聲明 effect
,如 Placement
為 2 (0000 0010),Update
為 4 (0000 0100)。一個 fiber node
可同時標(biāo)記多個 effect
,如函數(shù)組件 props
發(fā)生變化且使用了 useEffect hook
,那么就可以使用 Placement | Update = 516(位運算符)
來標(biāo)記。
收集帶effect的fiber node
如果一個 fiber node
被標(biāo)記了 effect
,那么 react
就會在這個 fiber node
完成協(xié)調(diào)以后,將這個 fiber node
收集到effectList
中。當(dāng)整顆 fiber tree
完成協(xié)調(diào)以后,所有被標(biāo)記 effect
的 fiber node
都被收集到一起。
收集fiber node
的 effectList
采用單鏈表結(jié)構(gòu)存儲,firstEffect
指向第一個標(biāo)記 effect
的 fiber node
,lastEffect
標(biāo)記最后一個 fiber node
,節(jié)點之間通過 nextEffect
指針連接。
由于 fiber tree
協(xié)調(diào)時采用的順序是深度優(yōu)先,協(xié)調(diào)完成的順序是子節(jié)點、子節(jié)點兄弟節(jié)點、父節(jié)點,所以收集帶 effect
標(biāo)記的 fiber node
時,順序也是子節(jié)點、子節(jié)點兄弟節(jié)點、父節(jié)點。
Render
render
也稱為commit
,是對協(xié)調(diào)過程中標(biāo)記的effect
的處理
effect
的處理分為三個階段,這三個階段按照從前到后的順序為:
1.before mutation
階段 (dom
操作之前)
2.mutation
階段 (dom
操作)
3.layout
階段 (dom
操作之后)
不同的階段,處理的 effect
種類也不相同。在每個階段,react 都會從 effectList
鏈表的頭部 - firstEffect
開始,按序遍歷 fiber node
, 直到 lastEffect
。
before mutation階段
before mutation
階段的主要工作是處理帶 Snapshot
標(biāo)記的 fiber node
。 從 firstEffect
開始遍歷 effect
列表,如果 fiber node
帶 Snapshot
標(biāo)記,觸發(fā) getSnapshotBeforeUpdate
方法。
mutation階段
mutation
階段的主要工作是處理帶 Deletion
、 Placement
、PlacementAndUpdate
、Update
標(biāo)記的 fiber node
。 在這一階段,涉及到 dom
節(jié)點的更新、新增、移動、刪除,組件節(jié)點刪除導(dǎo)致的 componentWillUnmount
、destory
方法的觸發(fā),以及刪除節(jié)點引發(fā)的 ref
引用的重置。
dom
節(jié)點的更新:
- 通過原生的 API
setAttribute
、removeArrribute
修改dom
節(jié)點的attr
; - 直接修改
dom
節(jié)點的style
; - 直接修改
dom
節(jié)點的innerHtml
、textContent
;
dom
節(jié)點的新增和移動:
- 如果新增(移動)的節(jié)點是父節(jié)點的最后一個子節(jié)點,那么可以直接使用
appendChild
方法。 - 如果不是最后一個節(jié)點,需要使用
insertBefore
方法。通過遍歷找到第一個沒有帶Placement
標(biāo)記的節(jié)點作為insertBefore
的定位元素。
dom
節(jié)點的刪除:
- 如果節(jié)點是
dom
節(jié)點,通過removeChild
移除; - 如果節(jié)點是組件節(jié)點,觸發(fā)
componentWillUnmount
、useEffect
的destory
方法的執(zhí)行; - 如果標(biāo)記
Deletion
的節(jié)點的子節(jié)點中有組件節(jié)點,深度優(yōu)先遍歷子節(jié)點,依次觸發(fā)子節(jié)點的componentWillUnmount
、useEffect
的destory
方法的執(zhí)行; - 如果標(biāo)記
Deletion
的節(jié)點及子節(jié)點關(guān)聯(lián)了ref
引用,要將ref
引用置空,及ref.current
=null
(也是深度優(yōu)先遍歷);
layout 階段
layout
階段的主要工作是處理帶 update
標(biāo)記的組件節(jié)點和帶 ref
標(biāo)記的所有節(jié)點。 工作內(nèi)容如下:
- 如果類組件節(jié)點是
mount
操作,觸發(fā)componentDidMount
;如果是update
操作,觸發(fā)componentDidUpdate
; - 如果函數(shù)組件節(jié)點時
mount
操作,觸發(fā)useLayoutEffect
的callback
;如果是update
操作,先觸發(fā)上一次更新生成的destory
,再觸發(fā)這一次的callback
; - 異步調(diào)度函數(shù)組件的
useEffect
; - 如果組件節(jié)點關(guān)聯(lián)了
ref
引用,要初始化ref.current
;
到此這篇關(guān)于React更新渲染原理深入分析的文章就介紹到這了,更多相關(guān)React更新渲染內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
react的ui庫antd中form表單使用SelectTree反顯問題及解決
這篇文章主要介紹了react的ui庫antd中form表單使用SelectTree反顯問題及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-01-01React中的useEffect useLayoutEffect到底怎么用
這篇文章主要介紹了React中的useEffect useLayoutEffect具體使用方法,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2023-02-02React路由規(guī)則定義與聲明式導(dǎo)航及編程式導(dǎo)航分別介紹
這篇文章主要介紹了React路由規(guī)則的定義、聲明式導(dǎo)航、編程式導(dǎo)航,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2022-09-09React-RouterV6+AntdV4實現(xiàn)Menu菜單路由跳轉(zhuǎn)的方法
這篇文章主要介紹了React-RouterV6+AntdV4實現(xiàn)Menu菜單路由跳轉(zhuǎn),主要有兩種跳轉(zhuǎn)方式一種是編程式跳轉(zhuǎn)另一種是NavLink鏈接式跳轉(zhuǎn),每種方式通過實例代碼給大家介紹的非常詳細,需要的朋友可以參考下2022-08-08