前言
從事前端工作的第一個專案便是和同事協作構建一個大型SPA——企業後端管理平臺,應用架構類似於企業微信·管理平臺。使用React技術棧來完成前端開發,React-Router成為了必不可少的利器,無重新整理的元件切換讓使用者體驗更佳。
看了許多學習文件,也在工作中進行了實踐,此文用於總結React-Router。
解決的問題
使用路由可以自如流暢得嚮應用中新增,切換元件,並保持頁面與URL間的同步,這在SPA中應用廣泛。使用React-Router我們不需要手動尋找需要渲染的元件,無需編寫複雜的邏輯,只需完成相應的路由配置,其它交給路由來解決。
用例
import React from 'react'
import { render } from 'react-dom'
// 首先我們需要匯入一些元件...
import { Router, Route, Link } from 'react-router'
const App = React.createClass({
render() {
return (
<div>
<h1>App</h1>
{/* 把 <a> 變成 <Link> */}
<ul>
<li><Link to="/about">About</Link></li>
<li><Link to="/inbox">Inbox</Link></li>
</ul>
{/*
接著用 `this.props.children` 替換 `<Child>`
router 會幫我們找到這個 children
childern所對應的元件可見於路由規則的巢狀關係中
在此例中為About或Inbox或undefined
*/}
{this.props.children}
</div>
)
}
})
// 最後,我們用一些 <Route> 來渲染 <Router>。
// Router是路由容器,route為真實路由,路由之間可以進行巢狀。
// 這些就是路由提供的我們想要的東西。
React.render((
<Router>
<Route path="/" component={App}>
<Route path="about" component={About} />
<Route path="inbox" component={Inbox} />
<Route path="messages/:id" component={Message} />
</Route>
</Router>
), document.body)
複製程式碼
Router是一個React元件,作為路由容器來使用,Route中所包含的便是路由的規則。在使用時我們可以使用頁面的層次關係來搭建路由規則。
在Route中有兩個重要的屬性:
- path屬性置頂路由的匹配規則,將顯示在瀏覽器的位址列在域名後面。path屬性省略時,總是會載入這個路由節點的指定元件。
- component屬性指定URL匹配此路由規則時,所要掛載的元件。
通過上面的配置,此應用會以如下方式渲染URL:
URL | 元件 |
---|---|
/ | App |
/about | App -> About |
/inbox | App -> Inbox |
/messages/[someid] | App -> Message |
路徑語法
路由路徑是匹配一個(或一部分)URL的一個字串模式。
你是否觀察到路由規則的第三條有些不太一樣,它的路徑是path="messages/:id",並未確切的指定一個路由地址。
在React-Router中有幾種特殊的匹配規則:
:paramName
匹配一段位於/
、?
或#
之後的URL。()
在其內部的規則被是可選的,匹配時可忽略。*
匹配任意字元(非貪婪的)知道下一個字元或者整個URL的末尾。
<Route path="/hello/:name"> // 匹配 /hello/michael 和 /hello/ryan
<Route path="/hello(/:name)"> // 匹配 /hello, /hello/michael 和 /hello/ryan
<Route path="/files/*.*"> // 匹配 /files/hello.jpg 和 /files/path/to/hello.jpg
複製程式碼
我們可以使用如用例的相對地址,也可以使用如下的絕對地址,當路由規則巢狀很深時,這大有用處,可以簡化我們的路由規則。
React.render((
<Router>
<Route path="/" component={App}>
<IndexRoute component={Index} />
<Route path="about" component={About} />
<Route path="inbox" component={Inbox}>
{/* 使用 /messages/:id 替換 messages/:id */}
<Route path="/messages/:id" component={Message} />
</Route>
</Route>
</Router>
), document.body)
複製程式碼
通過上面的配置,此應用會以如下方式渲染URL:
URL | 元件 |
---|---|
/ | App -> Index |
/about | App -> About |
/inbox | App -> Inbox |
/messages/[someid] | App -> Inbox -> Message |
提醒: 絕對路徑在動態路由中無法使用。
History
React-Router是建立在history之上的。history知道如何去監聽瀏覽器位址列的變化, 並解析這個URL轉化為location物件, 然後router使用它匹配到路由,最後正確地渲染對應的元件。
常用的history有三種形式:
- browserHistory
- hashHistory
- createMemoryHistory
使用方法是將history傳入中:
// JavaScript 模組匯入(譯者注:ES6 形式)
import { browserHistory } from 'react-router'
render(
<Router history={browserHistory} />,
document.getElementById('app')
)
複製程式碼
React-Router推薦使用BrosbrowserHistoryerHistory
,它使用瀏覽器中的History API來處理URL。伺服器需要進行相應配合,需要伺服器作好處理URL的準備。
hashHistory
使用URK中的hash(#)部分建立形如 example.com/#/some/path 的路由。
createMemoryHistory
用作SSR(服務端渲染),此處不作深入瞭解。
IndexRoute 和 Redirect
IndexRoute
當我們訪問的連結為www.demo.html/
時,頁面將載入App元件。但是此時,App的render中的this.props.children為undefined,因為我們的路由並不匹配App的子元件。此時,我們可以使用IndexRoute
來設定一個預設頁面。改造路由如下:
React.render((
<Router>
<Route path="/" component={App}>
<IndexRoute component={Index}>
<Route path="about" component={About} />
<Route path="inbox" component={Inbox} />
<Route path="messages/:id" component={Message} />
</Route>
</Router>
), document.body)
複製程式碼
此時,App的render中的this.props.children將會是元件。
Redirect
思考一個問題,如果在位址列輸入了路由規則中不匹配的URL,如www.demo.html/#/noroute
,會發生什麼?頁面會一片空白,空空如也。
我們有這樣一個需求,訪問www.demo.html/#/inbox
時,我想讓地址跳轉到www.demo.html/#/about
,渲染About元件,或是修正上面的空白錯誤,跳轉到About元件。
要解決這樣的問題,我們可以使用Redirect來進行重定向。
React.render((
<Router>
<Route path="/" component={App}>
<IndexRoute component={Index}>
<Route path="about" component={About} />
<Route path="inbox" component={Inbox} />
<Route path="messages/:id" component={Message} />
{/* 跳轉 /inbox 到 /about */}
<Redirect from="inbox" to="about" />
</Route>
</Router>
), document.body)
複製程式碼
路由跳轉
路由跳轉可以分為內部跳轉和外部跳轉,內部跳轉使用Link來完成,原理如a標籤一般。元件外跳轉可使用push方法來完成。
Link
Link元件用於取代a標籤,生成一個連結,允許使用者點選後跳轉到另一個路由,如用例中的使用方法。activeStyle屬性可以為當前路由新增樣式。或者使用activeClassName為當前路由新增class。
<Link to="/about" activeStyle={{color: 'red'}}>About</Link>
<Link to="/inbox" activeStyle={{color: 'green'}}>Inbox</Link>
複製程式碼
push
需要在提交表單,點選按鈕時跳轉,我們可以使用push方法完成。
// somewhere like a redux/flux action file:
import { browserHistory } from 'react-router'
browserHistory.push('/some/path')
複製程式碼
IndexLink
如果你在這個app中使用<Link to="/">Home</Link>
,它會一直處於啟用狀態,activeStyle和activeClassName會失效,或者說總是生效。因為所有的路由規則的開頭都是 / 。這確實是個問題,因為我們僅僅希望在Home被渲染後,啟用並連結到它。如果需要在Home路由被渲染後才啟用的指向 / 的連結,即實現精確匹配,/
只匹配根路由,可以使用 <IndexLink to="/">Home</IndexLink>
。
或者使用Link元件的onlyActiveOnIndex屬性,也可以達到同樣的效果。
<Link to="/" activeClassName="active" onlyActiveOnIndex={true}>
Home
</Link>
複製程式碼
動態路由
React-Router(Versoin 4)實現了動態路由。
對於大型應用來說,一個首當其衝的問題就是所需載入的JavaScript的大小。程式應當只載入當前渲染頁所需的JavaScript。有些開發者將這種方式稱之為“程式碼分拆” —— 將所有的程式碼分拆成多個小包,在使用者瀏覽過程中按需載入。React-Router 裡的路徑匹配以及元件載入都是非同步完成的,不僅允許你延遲載入元件,並且可以延遲載入路由配置。Route可以定義 getChildRoutes,getIndexRoute 和 getComponents 這幾個函式。它們都是非同步執行,並且只有在需要時才被呼叫。我們將這種方式稱之為 “逐漸匹配”。 React-Router 會逐漸的匹配 URL 並只載入該URL對應頁面所需的路徑配置和元件。
const CourseRoute = {
path: 'course/:courseId',
getChildRoutes(location, callback) {
require.ensure([], function (require) {
callback(null, [
require('./routes/Announcements'),
require('./routes/Assignments'),
require('./routes/Grades'),
])
})
},
getIndexRoute(location, callback) {
require.ensure([], function (require) {
callback(null, require('./components/Index'))
})
},
getComponents(location, callback) {
require.ensure([], function (require) {
callback(null, require('./components/Course'))
})
}
}
複製程式碼
Hook生命週期鉤子
Route可以定義onEnter和onLeave兩個hook,用於捕捉進入路由和離開路由的時間點,執行一些操作,如完成許可權驗證。
在路由跳轉過程中,onLeave hook 會在所有將離開的路由中觸發,從最下層的子路由開始直到最外層父路由結束。然後onEnter hook會從最外層的父路由開始直到最下層子路由結束。和事件捕獲與事件冒泡的機制相同。
<Router>
<Route path="about" component={About} onEnter={/*dosomething*/} />
<Route path="inbox" component={Inbox}>
<Redirect from="messages/:id" to="/messages/:id" onLeave={/*dosomething*/} />
</Route>
</Router>
複製程式碼
上面的程式碼中,如果使用者離開/messages/:id,進入/about時,會依次觸發以下的鉤子。
/messages/:id的onLeave
/inbox的onLeave
/about的onEnter
複製程式碼
routerWillLeave
是離開當前路由時的生命週期鉤子。該方法如果返回false,將阻止路由的切換,否則就返回一個字串,在離開route前提示使用者進行確認。
import { Lifecycle } from 'react-router'
const Home = React.createClass({
// 假設 Home 是一個 route 元件,它可能會使用
// Lifecycle mixin 去獲得一個 routerWillLeave 方法。
mixins: [ Lifecycle ],
routerWillLeave(nextLocation) {
if (!this.state.isSaved)
return 'Your work is not saved! Are you sure you want to leave?'
},
// ...
})
複製程式碼
Corner
- 路由演算法會根據定義的順序自頂向下匹配路由,匹配第一個符合規則的路由。
- 可以通過
this.props.params.id
來獲取/:id
中的id。 - URL的查詢字串/foo?bar=baz,可以用
this.props.location.query.bar
獲取。