首先看一下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渲染章节。