首先看一下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作正則匹配判斷是否匹配。react-router
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進行關聯,以下所示:函數
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,匹配成功的則返回一個objectpost
return {
path, // /foo/:bar
url: // /foo/randal
isExact, // false
params: // {"bar": "randal"}
};
複製代碼
Route組件維護一個state(match),match的值來自於matchPath的執行結果,以下所示ui
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渲染章節。