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