React-router-6原理剖析
基本使用
從react-router-dom導(dǎo)出BrowserRouter放在最外層,使用Routes和Route注冊路由,Routes組件會根據(jù)當(dāng)前l(fā)ocation匹配路由,渲染對應(yīng)組件。支持嵌套路由,也就是一個路由允許有children,最終形成一個路由tree。當(dāng)location匹配到一個路由時,會渲染對應(yīng)路徑上的組件。
Router object
將聲明式的路由改寫成object形式的路由,子路由放在父路由的children字段內(nèi),使用導(dǎo)出的useRoutes hook得到需要渲染組件。
原理
需求分析
router需要提供的功能是:
提供注冊路由的方式,用戶指定path以及對應(yīng)的組件,
根據(jù)瀏覽器的location能夠匹配path,確定組件,然后渲染
需要支持嵌套路由,例如,如前文的路由信息,當(dāng)location匹配到/about時,需要渲染的組件時Layout和About,而About是嵌套路由對應(yīng)的組件,是在Layout組件的outlet里渲染
監(jiān)聽瀏覽器跳轉(zhuǎn),刷新
代碼結(jié)構(gòu)
React-router 6是使用yarn workspace組織monorepo,包含了react-router,react-router-dom, react-router-dom-v5-compat, react-router-native。先只考慮web端的。

react-router實現(xiàn)了router大部分的功能,包括Router,MemoryRouter,Routes,Route,useRoutes.....
react-router-dom實現(xiàn)了少數(shù)web端需要的功能,包括BrowserRouter, HashRouter, Link, NavLink,useLinkClickHandler, useSearchParams。
react-router-dom導(dǎo)入了react-router所有的導(dǎo)出,然后重新導(dǎo)出了這些變量。(re-export all the import from react-router)
路由注冊
react-router主打聲明式路由,jsx書寫路由規(guī)則是最基本的方式。
例如
涉及到的組件是Routes和Route,Route組件是一個用來標(biāo)識的函數(shù),內(nèi)部沒有任何邏輯,也不會被渲染,唯一的用處是承載路由相關(guān)的信息,讓Routes組件來收集起來使用。
Routes組件的功能是收集Route的路由信息,根據(jù)location匹配組件渲染。內(nèi)部使用了useRoutes進行路由匹配返回最終需要渲染的結(jié)果。所以如果直接提供router object,可以跳過Routes內(nèi)部收集重構(gòu)路由對象的步驟,直接useRoutes獲取匹配結(jié)果。
react運行時,Routes組件會以children的形式得到路由信息,如下圖

遍歷children收集路由信息時,可以從child里看出是否是Route組件(根據(jù)type屬性),從props屬性里獲取Route上書寫的path和element等匹配規(guī)則。這一步的目的就是得到前文Router object示例中的routes對象,即path及其對應(yīng)的element,以及它的子路由信息children。
Routes本質(zhì)上是一個React組件,它的功能就是根據(jù)路由配置信息匹配當(dāng)前l(fā)ocation,找到需要渲染的組件,然后渲染出來。只不過因為聲明式路由,所以多了重構(gòu)出router object這一步。
匹配渲染
匹配的參數(shù)分別是location和router object。react-router內(nèi)部使用了history包,在web端是使用該包創(chuàng)建了browserHistory對象,獲取到location信息作為當(dāng)前頁面的路徑信息,而將browserHistory對象作為操作瀏覽器history的對象(在最外層的Router里provide)。
總之,可以認(rèn)為已經(jīng)有了當(dāng)前路徑pathname和路由配置router object。目標(biāo)是匹配出需要渲染的組件。
react-router分為四步:
打平flatten
排序
匹配
渲染
打平
入?yún)⑹莚outer object

結(jié)果

打平的目的是將嵌套的router object轉(zhuǎn)換成一層的數(shù)組,同時對每一條route信息做了一些轉(zhuǎn)換。包括:將子路由path和parent連接起來得到完整的path,計算了一個score,將父路由route和子路由route都放到一個routesMeta里,也就是routesMeta里存儲了從根到葉子結(jié)果完整路徑的路由信息。
排序
將完整path按分隔符'/'拆開后,計算得分規(guī)則:
包含通配符*,扣2分
路徑有index標(biāo)記,加2分
動態(tài)param,3分 (如 /:id, id為動態(tài)的)
空字符, 1分
靜態(tài)字符串,10分
根據(jù)score從高到低排序
匹配
正則匹配先跳過
渲染
下圖是當(dāng)前pathname為‘/nothing-here’,最終匹配得到的routesMeta的結(jié)構(gòu)。

第一個對應(yīng)的是根路由的Layout組件,第二個是子路由NoMatch組件。需要達到的效果是將NoMatch組件正確渲染到Layout組件里Outlet的地方。
也就是最終效果是像下面的jsx一樣的,子路由的element是作為provider里的值傳遞給Layout中的Outlet使用的,Outlet內(nèi)部獲取到上層的outlet值直接渲染,就成功將子路由的element在父路由的element內(nèi)部渲染出來了。
所以最后渲染是倒著遍歷數(shù)組。
對子路由,創(chuàng)建如下的元素,
對父路由
每一步遍歷要做的是渲染當(dāng)前的element,為了正確渲染,需要在element外面Provider兩個值。而上一步子路由的結(jié)果又作為下一步的outlet的值,從而完成了outlet的功能。