React 路由狀態(tài)管理總結(jié)

一谓晌、依賴(lài)(Dependencies)

在一般 SPA 開(kāi)發(fā)中杯拐,路由的管理十分重要互拾。作為 React 技術(shù)體系中的一部分,官方維護(hù)的 React-Router 則是首選的路由庫(kù)萧落。

在應(yīng)用 Redux 模式后践美,React-Router 與 Redux 的配合引發(fā)了新的問(wèn)題,是否需要將路由納入 store 進(jìn)行管理铐尚?如何將路由納入 store 進(jìn)行管理拨脉?這些都是需要考慮的問(wèn)題。我們將在后文討論第一個(gè)問(wèn)題宣增,而為了解決上述第二個(gè)問(wèn)題玫膀,React-Router-Redux 這個(gè)輕量級(jí)的擴(kuò)展庫(kù)應(yīng)運(yùn)而生并得到廣泛應(yīng)用。

另外需要說(shuō)明的是爹脾,長(zhǎng)久以來(lái) React-Router 與 React-Router-Redux 是兩個(gè)獨(dú)立的庫(kù)帖旨,但在 React-Router 4.x 版本以后,React-Router-Redux 已經(jīng)成為了 React-Router 的一部分灵妨。

本文并不旨在介紹兩種依賴(lài)庫(kù)的具體用法(具體用法請(qǐng)參考官方文檔和教程)解阅,而主要闡述其實(shí)現(xiàn)方式和原理,總結(jié)具體的實(shí)踐方式和注意事項(xiàng)泌霍。在主要內(nèi)容之前货抄,首先簡(jiǎn)要介紹下兩個(gè)庫(kù)的功能:

  1. React-Router

    React-Router 做的最重要的事就是將瀏覽器 URL 與程序聯(lián)系起來(lái)(借助 history 庫(kù)),它為 React 提供了聲明式的路由系統(tǒng)朱转,通過(guò)其提供的導(dǎo)航組件蟹地,我們能夠方便地使用 URL 來(lái)控制狀態(tài)的變化和組件的切換。

  2. React-Router-Redux

    按照官方的說(shuō)法藤为,其實(shí)現(xiàn)了「deep integration of react-router and redux」怪与,即 React-Router 與 Redux 的深度集成,它將路由完全納入 store 中進(jìn)行管理缅疟,使 store 成為了 URL(或者說(shuō)是 history)的數(shù)據(jù)來(lái)源分别,也使我們能夠通過(guò) dispatch action 的方式來(lái)修改 URL。我們將在后文介紹它的實(shí)現(xiàn)原理存淫。

二耘斩、實(shí)踐

路由狀態(tài)并非一定要介入 Redux 架構(gòu)中。在一些簡(jiǎn)單的應(yīng)用場(chǎng)景下桅咆,只需要使用 React-Router 提供的聲明式組件(Router, Route, Link 等)即可方便的實(shí)現(xiàn) URL 導(dǎo)航煌往。在一些稍復(fù)雜的場(chǎng)景中,只要保證遵循 React 單向數(shù)據(jù)流動(dòng)方式轧邪,遵照使用方法刽脖,也可以完成進(jìn)行路由信息的讀取和觸發(fā)變更,其過(guò)程如下圖所示忌愚。(使用方法請(qǐng)參照 React-Router 文檔和教程)

image

但在這里曲管,我們主要討論將路由狀態(tài)納入 Redux 架構(gòu)中的情況。本部分的下文將分為兩部分:

  1. 手動(dòng)管理硕糊,也就是不使用 React-Router-Redux院水;

  2. 借助 React-Router-Redux 管理,這也是討論的重點(diǎn)简十。

2.1檬某、手動(dòng)管理

在不借助其他庫(kù),一種簡(jiǎn)單的做法是手動(dòng)將路由狀態(tài)納入 store 中管理螟蝙,當(dāng) URL 改變時(shí)同步修改 store 中的狀態(tài)恢恼。

image

如上圖,在手動(dòng)同步環(huán)節(jié)胰默,通過(guò)一套 Redux 機(jī)制场斑,實(shí)現(xiàn)了路由信息在 store 中的存儲(chǔ)。history 作為數(shù)據(jù)來(lái)源牵署,通過(guò)監(jiān)聽(tīng) history漏隐,當(dāng) URL 狀態(tài)改變時(shí) dispatch 相應(yīng) action (例如 type = LOCATION_CHANGE),通過(guò)添加的 reducer 將 location 信息同步到 store奴迅。通過(guò)這種方式青责,組件就可以獲取 store 中的 location 狀態(tài)信息,這也是目前 react-redux-starter-kit 采用的方式取具。

這種相對(duì)原始的方式有一定弊端:

  • 沒(méi)有將路由完全納入 Redux 管理脖隶。

  • 路由不支持 time travel。

  • history 實(shí)際也是 react-router 的路由數(shù)據(jù)來(lái)源者填,這就導(dǎo)致我們 store 中存儲(chǔ)的 location 數(shù)據(jù)與 react-router 并不一定同步浩村。(例如,這會(huì)導(dǎo)致文末討論的重復(fù)渲染問(wèn)題)

2.2占哟、使用 React-Router-Redux

下面我們討論文首提出的問(wèn)題一:是否需要將路由納入 store 進(jìn)行管理心墅。雖然在 react-router 4.x 版本后,react-router-redux 已經(jīng)成為其一部分榨乎,但官方還是就其是否應(yīng)該在項(xiàng)目中使用進(jìn)行了建議:

  1. 希望在項(xiàng)目中使用完全使用 store 管理路由數(shù)據(jù)

  2. 希望使用 dispatch action 的方式進(jìn)行導(dǎo)航(修改路由)

  3. 希望調(diào)試時(shí)路由支持 time travel

上面是使用 React-Router-Redux 的原則怎燥,當(dāng)然一定程度上也可以是決定將路由納入 store 管理的原則。我覺(jué)得還可以增加兩條:

  1. 項(xiàng)目抽象中蜜暑,路由信息應(yīng)該作為一種全局的狀態(tài)管理

  2. 有 Redux 強(qiáng)迫癥

2.2.1铐姚、原理

通過(guò)一張圖的方式來(lái)了解一下 React-Router-Redux 的實(shí)現(xiàn)原理。

image

上圖實(shí)際上也是 React-Router-Redux 如何將 URL 與 state 同步的過(guò)程,在程序中隐绵,主要是通過(guò)如下的幾個(gè)重要的 API 實(shí)現(xiàn)的:

  • routerMiddleware 與 routerReducer
    routerMiddleware 與 routerReducer 的共同作用之众,讓我們能夠處理兩種 action 類(lèi)型:一種類(lèi)型為 LOCATION_CHANGE,與手動(dòng)管理過(guò)程中相同依许,它負(fù)責(zé)修改 store 存儲(chǔ)棺禾;另一種類(lèi)型為 CALL_HISTORY_METHOD,這類(lèi) action 一般會(huì)在組件內(nèi)派發(fā)峭跳,它不負(fù)責(zé) state 的修改膘婶,通過(guò) routerMiddleware 后,會(huì)被轉(zhuǎn)去調(diào)用 history 方法(如 push, replace 等)蛀醉,以修改 URL 狀態(tài)悬襟。

  • **syncHistoryWithStore ** 顧名思義,這個(gè)方法就是處理路由與 store 中信息同步的重要方法拯刁。通過(guò)這個(gè)方法脊岳,我們能獲得一個(gè)新的、增強(qiáng)版的 history 對(duì)象筛璧,這個(gè)對(duì)象重寫(xiě)了 history的listen方法逸绎,原有的 history.listen只負(fù)責(zé) action (LOCATION_CHANGE) 的派發(fā),新的 history.listen則只監(jiān)聽(tīng) store 的變化(使用了 store.subscribe)夭谤,所以當(dāng)我們?cè)诔绦騼?nèi)調(diào)用 history.listen時(shí)棺牧,實(shí)際上是在監(jiān)聽(tīng) store 中的路由信息。

2.2.2朗儒、實(shí)踐:location as a prop

在實(shí)際項(xiàng)目應(yīng)用中颊乘,一種較為合理實(shí)踐方式如下。

image

即將 location 或子屬性(如 location.pathname 等)作為屬性信息逐層傳遞醉锄,傳遞給關(guān)注路由信息的子組件乏悄,這類(lèi)似于 react-router 原有的使用方法,區(qū)別是恳不,在改變 URL 時(shí)檩小,使用了 dispatch action 的方式。

三烟勋、建議

3.1规求、 謹(jǐn)慎地使用 state.routing

一般地,在使用 React-Router-Redux 時(shí)卵惦,路由信息在 store 中會(huì)以 routing.locationBeforeTransition 的形式體現(xiàn)阻肿。我們?cè)谏衔牡膶?shí)踐中并沒(méi)有直接從 store 中獲取這個(gè)狀態(tài),實(shí)際上官方也不建議這樣做沮尿,從名字來(lái)看丛塌,作者已經(jīng)明確提醒了我們這是一個(gè)變化中的值。

You should not read the location state directly from the Redux store. This is because React Router operates asynchronously (to handle things such as dynamically-loaded components) and your component tree may not yet be updated in sync with your Redux state. You should rely on the props passed by React Router, as they are only updated after it has processed all asynchronous code.

不應(yīng)該直接從 Redux store 中讀取路由狀態(tài)。這是因?yàn)?React-Router 的行為是異步的(例如為了處理組件動(dòng)態(tài)加載等)赴邻,所以你的組件樹(shù)可能不能跟上 Redux 狀態(tài)的變化印衔。應(yīng)該去依賴(lài) React Router 傳遞的屬性,這保證了這些值是在所有異步操作完成后才更新的乍楚。

當(dāng) routing 中的值已經(jīng)改變時(shí)当编,React-Router 可能還沒(méi)有將組件樹(shù)進(jìn)行更新完畢,如果使用這個(gè)值可能引發(fā)一些問(wèn)題徒溪。所以作者依然建議我們采用傳遞 location 屬性的方式讀取路由信息,以確保 React-Router 已經(jīng)處理完畢金顿。

3.2臊泌、只傳遞必要的路由信息

只將必要信息作為 prop 傳遞,例如 location.pathname揍拆、 location.query.page渠概,而不是傳遞整個(gè) location。這能夠盡量避免可能的重復(fù)渲染嫂拴。

3.3播揪、 只使用 dispatch action 的方式修改路由

實(shí)際上,除了使用 Link 組件筒狠,使用 React-Router-Redux 后有多種方式能夠修改路由信息猪狈,如:

  1. history.method

  2. context.router.method

  3. dispatch ROUTER-ACTION

筆者仍然建議只使用 dispatch action 方式修改路由,這種方式更為遵循 Redux 流程辩恼,同時(shí)方便組件的解耦雇庙。在實(shí)際應(yīng)用中,應(yīng)該使用統(tǒng)一的 Action Creator 來(lái)創(chuàng)建修改路由的 action灶伊。

3.4疆前、 謹(jǐn)慎地使用 withRouter高階組件(裝飾器)

React-Router 提供了withRouter高階組件以便組件訪(fǎng)問(wèn)路由狀態(tài)信息(match, location, history),但同時(shí)一旦引用的路由屬性發(fā)生變化就會(huì)觸發(fā)重渲染流程聘萨,如果使用不當(dāng)竹椒,則可能導(dǎo)致組件進(jìn)行多余的重復(fù)渲染。

四米辐、常見(jiàn)問(wèn)題

4.1胸完、re-render(重復(fù)渲染)問(wèn)題

在使用 React-Router 和路由組件異步加載后,一個(gè)常見(jiàn)的問(wèn)題是組件切換時(shí)發(fā)生意外的重復(fù)渲染儡循。 一般情況下(未進(jìn)行代碼分割時(shí))舶吗,React-Router 在切換路由組件時(shí),過(guò)程是這樣的:

image

在進(jìn)行了代碼分割后择膝,路由組件改為異步加載誓琼,過(guò)程變成了這樣:

image

由于組件 A 將 location 或其相關(guān)屬性最為屬性 props 傳入,location 的變化導(dǎo)致了 props 的改變,此時(shí)由于組件 B 還未加載成功腹侣,導(dǎo)致組件 A 在卸載前進(jìn)行沒(méi)有必要的重渲染叔收。

這個(gè)問(wèn)題一般是因?yàn)殄e(cuò)誤地使用了變化的路由信息,如上文中的 state.routing 信息傲隶,由于 state.routing 與 React-Router 路由信息不同步造成的饺律。解決辦法:參照上文提出的實(shí)踐,使用 Route 組件注入的 location 數(shù)據(jù)進(jìn)行路由信息傳遞跺株。

五复濒、參考

  1. https://github.com/reactjs/react-router-redux

  2. https://github.com/reactjs/react-router-tutorial

  3. https://github.com/ReactTraining/history

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市乒省,隨后出現(xiàn)的幾起案子巧颈,更是在濱河造成了極大的恐慌,老刑警劉巖袖扛,帶你破解...
    沈念sama閱讀 216,470評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件砸泛,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡蛆封,警方通過(guò)查閱死者的電腦和手機(jī)唇礁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)惨篱,“玉大人盏筐,你說(shuō)我怎么就攤上這事《噬撸” “怎么了机断?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,577評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)绣夺。 經(jīng)常有香客問(wèn)我吏奸,道長(zhǎng),這世上最難降的妖魔是什么陶耍? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,176評(píng)論 1 292
  • 正文 為了忘掉前任奋蔚,我火速辦了婚禮,結(jié)果婚禮上烈钞,老公的妹妹穿的比我還像新娘泊碑。我一直安慰自己,他們只是感情好毯欣,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布馒过。 她就那樣靜靜地躺著,像睡著了一般酗钞。 火紅的嫁衣襯著肌膚如雪腹忽。 梳的紋絲不亂的頭發(fā)上来累,一...
    開(kāi)封第一講書(shū)人閱讀 51,155評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音窘奏,去河邊找鬼嘹锁。 笑死,一個(gè)胖子當(dāng)著我的面吹牛着裹,可吹牛的內(nèi)容都是我干的领猾。 我是一名探鬼主播,決...
    沈念sama閱讀 40,041評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼骇扇,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼摔竿!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起匠题,我...
    開(kāi)封第一講書(shū)人閱讀 38,903評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤拯坟,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后韭山,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,319評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡冷溃,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評(píng)論 2 332
  • 正文 我和宋清朗相戀三年钱磅,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片似枕。...
    茶點(diǎn)故事閱讀 39,703評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡盖淡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出凿歼,到底是詐尸還是另有隱情褪迟,我是刑警寧澤,帶...
    沈念sama閱讀 35,417評(píng)論 5 343
  • 正文 年R本政府宣布答憔,位于F島的核電站味赃,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏虐拓。R本人自食惡果不足惜心俗,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蓉驹。 院中可真熱鬧城榛,春花似錦、人聲如沸态兴。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,664評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)喘垂。三九已至甜刻,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間王污,已是汗流浹背罢吃。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,818評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留昭齐,地道東北人尿招。 一個(gè)月前我還...
    沈念sama閱讀 47,711評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像阱驾,于是被迫代替她去往敵國(guó)和親就谜。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容