react-router 升級小記

發表於2018-04-02

前言

最近將公司專案的 react-router 從 v3 版本升到了 v4 版本,react-router v4 跟 v3 完全不相容,是一次徹底的重寫。這也給升級造成了極大的困難,與其說升級不如說是對 router 層重寫。之前我也將專案的 react 從 v15 版本升級到了 v16 版本,相較而言升級 react-router 比升級 react 困難多了。升級過程中踩了不少的坑,也有一些值得分享的點。寫成一篇小文,供大家參考。

依賴升級

react-router v4 跟 react 一樣拆成了兩部分,核心的 react-router 和依執行環境而定的 react-router-dom 或 react-router-native(跟 react-dom 和 react-native 一樣)。本文要說的是瀏覽器環境,也就是 react-router + react-router-dom

先安裝依賴(推薦使用 yarn)

為什麼要安裝 history 後面會解釋。

元件外導航與 react-router-redux

之前我們專案中使用了 react-router-redux 你有很多理由使用它,但對於我們來說唯一的理由或者用處就是用於在頁面元件之外導航,react-router-redux 讓你可以在任何地方通過 dispatch 處理頁面跳轉,如:store.dispatch(push(‘/’))。因為這個我們就必須使用 react-router-redux 嗎?當然不需要,有更簡單的辦法實現這個需求。所以這次升級我移除了react-router-redux, 寫作此文時支援 react-router v4 的 react-router-redux 還處於 v5.0.0-alpha.7 也是原因之一。

還記得之前安裝的 history 嗎?history 是 react-router 唯二的主要依賴之一,之所以要顯式安裝,是因為我們要使用它來實現頁面元件外導航。以下以 browser history 為例(hash history 和 memory history 都是一樣的):

我們不使用 react-router-dom 提供的 BrowserRouter 而是自己實現一個

搞定!就這麼簡單,這樣在任何地方只要引用 history 就可以使用它進行導航操作,如 history.push(‘/’),更多使用方式請參考 history 文件。其實 react-router-dom 的 BrowserRouter 跟我們做了同樣的事,區別在於我們這麼做能把 history 暴露出來。這個 history 就是頁面元件 props 裡面的 history 自然也就能做同樣的事情。

靜態配置

react-router v3 是面向配置的,元件寫法只是一種語法糖。而 react-router v4 是完全面向元件的,提供的 Route Switch 等都是真正的元件。這也就導致只能按元件的方式寫路由,不能寫配置。但是 v3 那樣的配置確實有一些方便之處,如統一管理、使用方便等。

多虧 JSX 靈活的語法,我們依然有辦法按配置的方式寫 react-router v4 的路由。

這樣我們就用配置的方式寫出了面向元件的路由,兼顧兩者的優點。如果有巢狀路由需求,可以參考官方示例。官方也提供了一個 react-router-config, 不過我沒有使用,一來覺得沒必要,二來寫作此文時它還處於 v1.0.0-beta.4 版本。

非同步元件與 Code Splitting

Web 應用最大的一個優勢就是不必下載整個應用,只用下載需要的部分就可以使用。要達到這樣的目標,就需要對程式碼進行分片,非同步載入元件。可惜 react-router v4 沒有像 v3 一樣提供載入非同步元件的介面。這部分工作就需要我們自己來處理。

我們可以建立一個高階元件 Bundle,專門用來載入非同步元件。

然後修改一下 routes.js

這樣每個頁面都會打包成單獨的 JS,訪問相應頁面才會去非同步載入對應的元件。這樣也可以做精細化快取控制。
需要注意的是 import() 語法在寫作本文時還處於 Stage 2 的狀態,需給 Babel 新增 syntax-dynamic-import 外掛才能正常工作,另外需 webpack 2 及以上才支援。

查詢引數

因為各種原因 react-router v4 不再解析 ?key=value 這樣的 URL 的查詢引數,頁面元件 props.location 中只有 search 字串。這跟 v3 不相容,而且很不方便。我們有辦法相容一下嗎?當然有,這時候之前寫的 histroy.js 又有新的用處了。

這樣我們就能在頁面元件 props.location.query 拿到解析好的 URL 查詢引數了,跟 v3 完美相容。還有個額外的好處是在任何地方引用 history 都可以拿到解析好的 URL 查詢引數。需要注意的是,在 history 的設計中,history 物件是 Mutable 的,所以我們可以直接修改 history。但是 history.location 是 Immutable 的,所以我們要確保每一個 location 物件都是全新的。

搭配 Redux

react-router v4 跟 redux 搭配有一個大坑(mobx 應該也有同樣的問題),詳情請看這篇文章,這裡就不再贅述。簡單來說,如果一個元件用 redux 的 connect 包裝過,又️不是 Route 的子元件,那麼 history 的變更就不會觸發這個元件的更新,它的子元件自然也不會更新。比如應用的根元件(上文的 App)。

解決方案也很簡單,可以用 react-router v4 提供的 withRouter 再包裝一遍:withRouter(connect(…)(App)),或者讓 App 做為 Router 的子元件,原理都一樣。我採用的後者。

最後

不得不說升級 react-router 很困難,坑也很多。但是把坑一個個填完,最終完美升級也是一件很有意思,很有成就感的事。希望這篇文章能對你有所幫助。

另外完整的 Demo 請戳我的 GitHub,喜歡的話點個 Star 吧 :P

相關文章