首先看一下react-router官網示例react
const BasicExample = () => ( <Router> <Route exact path="/" component={Home} /> <Route path="/about" component={About} /> <Route path="/topics" component={Topics} /> </div> </Router> ); 複製代碼
上面代碼的運行效果點擊這裏web
本文的目的是講清楚react-router如何根據瀏覽器中的url來渲染不一樣的組件的,至於url是如何改變的(Link組件)請參見下一篇react-router原理之Link跳轉。正則表達式
react-router提供了專門的路由匹配方法matchPath(位於packages/react-router/modules/matchPath.js),該方法背後依賴的實際上是path-to-regexp包。數組
path-to-regexp輸入是路徑字符串(也就是Route中定義的path的值),輸出包含兩部分瀏覽器
針對path中的參數(下例中的:bar)path-to-regexp在生成正則的時候會把它做爲一個捕獲組進行定義,同時把參數的名字(bar)記錄到數組keys中bash
var pathToRegexp = require('path-to-regexp') var keys = [] var re = pathToRegexp('/foo/:bar', keys) console.log(re); console.log(keys); // 輸出 /^\/foo\/([^\/]+?)(?:\/)?$/i [ { name: 'bar', prefix: '/', delimiter: '/', optional: false, repeat: false, partial: false, pattern: '[^\\/]+?' } ] 複製代碼
matchPath方法首先經過path-to-regexp的方法來獲取Route上定義的path對應的正則,再將生成的正則表達式與url中的pathname作正則匹配判斷是否匹配。markdown
console.log(re.exec('/foo/randal')); console.log(re.exec('/foos/randal')); // 輸出 [ '/foo/randal', 'randal', index: 0, input: '/foo/randal' ] null 複製代碼
因爲path-to-regexp建立的正則中對param部分建立了捕獲組,同時把param的key記錄在了單獨的數組keys中,所以經過遍歷正則匹配的結果和keys數組便可將param的key和value進行關聯,以下所示:react-router
const match = re.exec('/foo/randal'); const [url, ...values] = match; const params = keys.reduce((memo, key, index) => { memo[key.name] = values[index]; return memo; }, {}) console.log(params) // {"bar": "randal"} 複製代碼
最終matchPath針對未匹配的返回null,匹配成功的則返回一個object函數
return { path, // /foo/:bar url: // /foo/randal isExact, // false params: // {"bar": "randal"} }; 複製代碼
Route組件維護一個state(match),match的值來自於matchPath的執行結果,以下所示oop
state = { match: this.computeMatch(this.props, this.context.router) }; computeMatch({ computedMatch, location, path, strict, exact, sensitive }, router) { if (computedMatch) return computedMatch; // computedMatch留給Switch使用 const { route } = router; const pathname = (location || route.location).pathname; return matchPath(pathname, { path, strict, exact, sensitive }, route.match); } 複製代碼
當state.match不爲null的時候Route纔會建立關聯的component。
Route關聯component有多種形式(render、component、children) children定義形式與render和component的不一樣在於,children的執行與match無關,即便match爲null,children函數也是會執行的,至於爲何會有children這樣的設計呢,在接下來的一篇關於Link組件的文章中會提到。
render() { const { match } = this.state; const { children, component, render } = this.props; const props = { match, ...}; if (component) return match ? React.createElement(component, props) : null; if (render) return match ? render(props) : null; if (typeof children === "function") return children(props); return null; } 複製代碼
至此關於react-router如何根據url渲染不一樣Route的組件都講解完了,不過有時候只用Route的話仍是會產生問題,好比:
<Route path="/about" component={About}/> <Route path="/:user" component={User}/> <Route component={NoMatch}/> 複製代碼
若是當前訪問的url是/about的話,上面的寫法會在頁面上渲染About、User、NoMatch三個組件,其實咱們但願的是隻渲染About組件。
針對上面的問題,能夠用Switch組件包裹一下
<Switch> <Route path="/about" component={About}/> <Route path="/:user" component={User}/> <Route component={NoMatch}/> </Switch> 複製代碼
通過Switch包裹後, 若是訪問url是/about的話則只會渲染About組件了,若是url是/abouts的話,則只會渲染User組件。
Switch組件的特色是隻會從子children裏挑選一個Route渲染,爲了實現只渲染一個的目的,Switch採用的是Route路徑匹配前置,不依賴Route的render方法來渲染組件,而是在Switch中就開始Route的路徑匹配,一旦發現一個匹配的路徑,則將其挑選出來進行渲染。Switch的關鍵代碼以下
render() { const { route } = this.context.router; const { children } = this.props; const location = this.props.location || route.location; let match, child; // 子children至關於只是選項,Switch負責從中挑選與當前url匹配的Route,被選中的子Route纔會觸發render方法 React.Children.forEach(children, element => { if (match == null && React.isValidElement(element)) { const { path: pathProp, exact, strict, sensitive, from } = element.props; const path = pathProp || from; child = element; match = matchPath( location.pathname, { path, exact, strict, sensitive }, route.match ); } }); return match ? React.cloneElement(child, { location, computedMatch: match }) : null; } 複製代碼
上面代碼把matchPath的執行結果match以computedMatch爲key傳入到Route中了,這樣就避免了重複匹配,Route的computeMatch方法就能夠直接複用了,computeMatch代碼參見前面的Route渲染章節。