React Router 是專為 React 設計的路由解決方案,在使用 React 來開發 SPA (單頁應用)專案時,都會需要路由功能,而 React Router 應該是目前使用率最高的。
React Router 並不是 Facebook 的 React 官方團隊開發的,但是據說有官方人員參與開發。React Router 的設計思想來源於 Ember 的路由,如果原來有用過 Ember 的路由,那麼應該對 React Router 不會陌生。
什麼是路由?
對於沒有開發過後端,也沒有開發過 SPA 的前端來說,「路由」這個名詞可能會讓人比較困惑,這裡的路由並不是指「硬體路由」,也不是網路七層協議中的「網路層路由」,但是其思想原理是一樣的。我儘量簡單通俗的介紹一下。
假如我們有一臺提供 Web 服務的伺服器的網路地址是:10.0.0.1
,而該 Web 服務又提供了三個可供使用者訪問的頁面,其頁面 URI 分別是:
1 2 3 |
http://10.0.0.1/ http://10.0.0.1/about http://10.0.0.1/concat |
那麼其路徑就分別是 /
,/about
,/concat
。
當使用者使用 http://10.0.0.1/about
來訪問該頁面時,Web 服務會接收到這個請求,然後會解析 URI 中的路徑 /about
,在 Web 服務的程式中,該路徑對應著相應的處理邏輯,程式會把請求交給路徑所對應的處理邏輯,這樣就完成了一次「路由分發」,這個分發就是通過「路由」來完成的。
前端路由
前端的路由和後端的路由在實現技術上不一樣,但是原理都是一樣的。在 HTML5 的 history
API 出現之前,前端的路由都是通過 hash
來實現的,hash
能相容低版本的瀏覽器。如果我們把上面例子中提到的 3 個頁面用 hash
來實現的話,它的 URI 規則中需要帶上 #
。
1 2 3 |
http://10.0.0.1/ http://10.0.0.1/#/about http://10.0.0.1/#/concat |
Web 服務並不會解析 hash
,也就是說 #
後的內容 Web 服務都會自動忽略,但是 JavaScript 是可以通過 window.location.hash
讀取到的,讀取到路徑加以解析之後就可以響應不同路徑的邏輯處理。
history 是 HTML5 才有的新 API,可以用來操作瀏覽器的 session history (會話歷史)。基於 history
來實現的路由可以和最初的例子中提到的路徑規則一樣。
1 2 3 |
http://10.0.0.1/ http://10.0.0.1/about http://10.0.0.1/concat |
使用者可能都察覺不到該訪問地址是 Web 服務實現的路由還是前端實現的路由。
從效能和使用者體驗的層面來比較的話,後端路由每次訪問一個新頁面的時候都要向伺服器傳送請求,然後伺服器再響應請求,這個過程肯定會有延遲。而前端路由在訪問一個新頁面的時候僅僅是變換了一下路徑而已,沒有了網路延遲,對於使用者體驗來說會有相當大的提升。
說了這麼多的「路由基礎」,該回頭來說說 React Router 了。
配置路由
使用 React Router 來配置上面例子中的三個頁面,每個頁面分別對應著一個 React Component。
/about
頁面的入口檔案 about.js
1 2 3 4 5 6 7 8 9 10 11 |
import React from 'react'; class About extends React.Component { render () { return ( This is About page. ); } }; export default About; |
/concat
頁面的入口檔案 concat.js
1 2 3 4 5 6 7 8 9 |
import React from 'react'; const Concat = () => { return ( This is Concat page. ); }; export default Concat; |
/
首頁對應的是 app.js,它也是整個 React Component 的入口檔案。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import React from 'react'; import {Link} from 'react-router'; class App extends React.Component { render () { return ( React Router Demo by stylechen.com Home About Concat {this.props.children} ) } }; export default App; |
app.js 中的 Link
元件是 React Router 提供的元件,用於連結到相應頁面。如果直接使用 a 標籤的話相當於頁面跳轉了,而使用 Link
只是應用內的路由跳轉,頁面跳轉意味著先重新載入整個頁面,然後才是應用內部的路由跳轉。
index.js 中包含了 route 的配置,同時在該檔案中對 React Component 進行 render
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import React from 'react'; import ReactDOM from 'react-dom'; import createBrowserHistory from 'history/lib/createBrowserHistory'; import App from './component/app'; import About from './component/about'; import Concat from './component/concat'; import {Router, Route} from 'react-router'; const history = createBrowserHistory(); const router = ( ); ReactDOM.render( router, document.getElementById('root') ); |
Route
元件就是用於配置路由,path
屬性用於配置路徑,component
就是對應的 React Component。
Route
元件支援巢狀,巢狀時子元件的路徑可以繼承父元件的路徑,上面的 about
巢狀後就成了 /about
,當然也可以直接以根路徑為開頭。
此時再看看 app.js 中的 this.props.children
,About
和 Concat
兩個頁面元件其實就是以這種形式插入到父元件中,只是插入的時候用 React Router 提供的元件再包裝了一下使它們可以支援路由。
Router
元件還有一個重要的屬性,那就是history
,這可以配置使用history
來實現路由,如果沒有配置這個屬性則預設使用hash
。
路由引數
假如我們有很多 list 頁面,這些頁面除了動態內容不同,其他的頁面部分都相同,這個時候需要怎麼配置路由和元件呢?
這種場景就需要用到路由的引數功能,增加一條包含引數的路由配置。
1 |
import List from './component/list'; |
注意 path
屬性中的 :id
就是該路由的引數(param
)。再來看看 List
頁面的元件。
/list
對應了 list.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import React from 'react'; class List extends React.Component { render () { return ( This is List page. The list page id is {this.props.params.id} ); } }; export default List; |
在 List
元件中,可以直接通過 this.props.params.id
來訪問實際的引數值(這裡的id key 就和定義路徑的 :id
相對應),React Router 將路由的資料都通過 props
傳遞給了頁面元件,這樣就可以非常方便的訪問路由相關的資料了。
元件的生命週期
每個 React Component 都有生命週期,按照常規的策略,當呼叫父元件的 render
的時候,會將所有的頁面子元件也進行 render
,這種邏輯顯然不合理了。那麼當一個 React 的應用有了路由功能後,它的生命週期會如何處理呢?
當切換路由的路徑的時候才去 render 對應的頁面元件,給 About
繫結兩個元件裝載和解除安裝的事件,就可以測試出來了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
import React from 'react'; class About extends React.Component { componentDidMount () { console.log('mount'); } componentWillUnmount () { console.log('un mount'); } render () { return ( This is About page. ); } }; export default About; |
在切換到 /about
頁面的時候,會列印出 mount
,而當離開頁面的時候會列印出 un mount
。路由的這種進入時裝載元件離開時解除安裝元件的策略就可以做到合理利用「資源」,不會一下把所有的元件都裝載進來使記憶體佔用飆升,也不會離開時沒有解除安裝而時記憶體洩漏。
React Router 還有很多比較方便 API,這裡不一一列舉,通過上面的文章基本能覆蓋到常規的使用場景了,想了解更多使用方面檢視 專案主頁。
本文的 demo 程式碼都已經託管在 github 上了,可以自己下載到本地瀏覽。