文章首发于:github.com/USTB-musion…html
用react-router v4能够实现单页面应用,能够将组件映射到路由上,将对应的组件渲染到想要渲染的位置。 react路由有两种方式:一种是HashRouter,即利用hash实现路由切换。另外一种是BrowserRouter,即利用html5 API实现路由的切换。本文是在阅读react-router v4源码以后简单的实现。html5
本文将从如下几部分进行总结:react
如下是参照react-router 官方文档实现的一个简单例子:git
import React, { Component } from 'react';
import { render } from 'react-dom';
import { HashRouter as Router, Route, Link, Redirect, Switch } from 'react-router-dom';
import Home from './Home';
import Profile from './Profile';
import User from './User';
export default class App extends Component {
constructor() {
super();
}
render() {
return (
<Router>
<div>
<div>
<Link to="/home">首页</Link>
<Link to="/profile">我的中心</Link>
<Link to="/user">用户</Link>
</div>
<div>
<Switch>
<Route path="/home" exact={true} component={Home}></Route>
<Route path="/profile" component={Profile}></Route>
<Route path="/user" component={User}></Route>
<Redirect to="/home"></Redirect>
</Switch>
</div>
</div>
</Router>
)
}
}
render(<App></App>, document.querySelector('#root'));
复制代码
这样就能实现一个超级简单的单页面应用,根据路径的变化渲染相应的组件。点击首页会跳转到Home组件,点击我的中心会跳转到Profile组件,点击用户会跳转到User组件。在这个例子当中。看下这行代码github
import { HashRouter as Router, Route, Link, Redirect, Switch } from 'react-router-dom';
复制代码
若是不引入'react-router-dom'这个包,而是本身实现一个my-react-router-dom,暴露出HashRouter,Route,Link,Redirect,Switch这几个组件,而且这几个组件和react-router-dom提供的功能基本同样,那究竟怎么实现呢?设计模式
// 这是index.js文件
import HashRouter from './HashRouter';
import Route from './Route';
import Link from './Link';
import Redirect from './Redirect';
import Switch from './Switch';
export {
HashRouter,
Route,
Link,
Redirect,
Switch
}
复制代码
再看一下context.js文件,context能够跨组件传递数据:api
// 这是context.js文件
import React, { Component } from 'react';
// 这个方法是16.3新增的
let { Provider, Consumer} = React.createContext();
export { Provider, Consumer};
复制代码
‘现有的原生 Context API 存在着一个致命的问题,那就是在 Context 值更新后,顶层组件向目标组件 props 透传的过程当中,若是中间某个组件的 shouldComponentUpdate 函数返回了 false,由于没法再继续触发底层组件的 rerender,新的 Context 值将没法到达目标组件。这样的不肯定性对于目标组件来讲是彻底不可控的,也就是说目标组件没法保证本身每一次均可以接收到更新后的 Context 值。’如何解读 react 16.3 引入的新 context api ---诚身的回答bash
新版 Context API 提供Provider和Consumer两个组件,顾名思义,provider(提供者)和Consumer(消费者):react-router
分析上面的例子,HashRouter的别名设置为Router,传入的 Router 中的每一项即为一条路由配置,表示在匹配给定的地址时,应该使用什么组件渲染视图。HashRouter的简单实现以下:dom
// 这是hashRouter.js文件
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Provider } from './context';
export default class HashRouter extends Component {
constructor() {
super();
this.state = {
location: {
// slice(1)将#截掉
pathname: window.location.hash.slice(1) || ''
}
};
}
componentDidMount() {
// 首次进入页面url会显示#/
// 如localhost:3000会显示为localhost:3000/#/
window.location.hash = window.location.hash || '/';
// 监听hash值变化,从新设置location状态
window.addEventListener('hashchange', () => {
this.setState({
location: {
...this.state.location,
pathname: window.location.hash.slice(1) || '/'
}
})
})
}
render() {
// 每一个子route对象都会包含location
let value = {
location: this.state.location,
history: {
push(to) {
window.location.hash = to;
}
}
}
return (
<Provider value={value}>
{this.props.children}
</Provider>
)
}
}
复制代码
这样一来,使用hashRouter的方式第一次进入页面时,将显示#/,如localhost:3000首次进入页面将会显示localhost:3000/#/,经过Provider组件来将location和history对象传递给子route,经过hashchange方法来监听hash值的变化,进而从新设置location的状态。
经过分析上面的例子,看这一行代码:
<Route path="/home" exact={true} component={Home}></Route>
复制代码
发现Route组件包含有path,exact,component这些属性,那如何实现Route组件呢?看Route.js文件:
// 这是route.js文件
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Consumer } from './context';
import pathToReg from 'path-to-regexp';
export default class Router extends Component {
constructor() {
super();
}
render() {
return (
<Consumer>
{state => {
// <route path="xx" component="xx" exact={true}></route>
// path是route传递的
let { path, component: Component, exact = false } = this.props;
// pathname是location中的
let pathname = state.location.pathname;
// 根据path实现一个正则,经过正则匹配
// location中的/home/123是能匹配到Home组件的
let keys = [];
let reg = pathToReg(path, keys, { end: exact});
keys = keys.map(item => item.name);
let result = pathname.match(reg);
let [url, ...values] = result || [];
// 实现路由跳转
let props = {
location: state.location,
history: state.history,
match: {
params: keys.reduce((obj, current, index) => {
obj[current] = values[index];
return obj;
}, {})
}
}
if (result) {
return <Component {...props}></Component>
}
return null
}}
</Consumer>
)
}
}
复制代码
利用path-to-regexp这个库来进行是否严格匹配路径,利用新版context的Consumer组件和props来取出path,component,axact这几个参数。若是匹配到path,则经过<Component {...props}>返回相应匹配到的组件。
分析上面的例子,Link的组件实现稍微简单一些,点击内容跳转到to属性对应的路径便可:
// 这是Link组件
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Consumer } from './context';
export default class Link extends Component {
constructor() {
super();
}
render() {
return (
<Consumer>
{state => {
return <a onClick={() => {
state.history.push(this.props.to);
}}>{this.props.children}</a>
}}
</Consumer>
)
}
}
复制代码
经过history.push便可实现点击this.props.children的内容跳转到Link组件的to属性对应的路径。
重定向就是匹配不到后直接跳转到Redirect中的to路径:
// 这是Redirect.js文件
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Consumer } from './context';
export default class Redirect extends Component {
constructor() {
super();
}
render() {
return (
<Consumer>
{state => {
// 重定向就是匹配不到后直接跳转到redirect中的to路径
state.history.push(this.props.to);
return null;
}}
</Consumer>
)
}
}
复制代码
Switch组件的做用就是只匹配第一个匹配到的组件:
// 这是Switch.js的文件
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Consumer } from './context';
import pathToRegExp from 'path-to-regexp';
// Switch的做用就是匹配一个组件
export default class Switch extends Component {
constructor() {
super();
}
render() {
return (
<Consumer>
{state => {
{
let pathname = state.location.pathname;
// 取出Switch包含的组件
let children = this.props.children;
for ( var i = 0; i < children.length; i++) {
let child = children[i];
// Redirect组件可能没有path属性
let path = child.props.path || '';
pathToRegExp(path, [], {end: false});
// switch匹配成功了
if (reg.test(pathname)) {
// 将匹配到的组件返回便可
return child;
}
}
return null;
}
}}
</Consumer>
)
}
}
复制代码
经过遍历this.props.children来进行逐一匹配,若是匹配到相应的路径,当即返回对应的组件,若是匹配不到,则返回空。
经过上面的例子,发现实现一个简易版的react-router并非想象中那么难。因为时间关系,路由权限校验,BrowserRouter,withRoute这些组件并无实现。下次有时间再好好分析思考一下如何实现:)
React 全新的 Context API —— qiqi105
重新的 Context API 看 React 应用设计模式 ——诚身
react-router@4.0 使用和源码解析 ——夏尔先生