手写那些年用过的React路由

认知

以前没有先后端分离的时候,路由几乎都是针对后台而言的,有人说掌控了路由就至关于占了主权地位,我以为这句话是不为过的。由于路由才能决定你的页面从哪里来,到哪里去。如今的先后端分离项目,路由几乎都给了前端处理,好比你常用的vue-router,react-router,今天就react路由为基础,一块儿实现下React开发中经常使用的路由那些个东东。前端

选择

react-router 仍是 react-router-dom? 在 React 的使用中,咱们通常要引入两个包,react 和 react-dom,那么 react-router 和react-router-dom 是否是两个都要引用呢?答案是它们两个只要引用一个就好了,二者区别是后者比前者多出了 <Link> <BrowserRouter> 这样的 DOM 类组件。 所以咱们只需引用 react-router-dom 这个包就好了。固然,若是搭配 redux ,你还须要使用 react-router-redux。 what's the diff between react-router-dom & react-router? #4648

HashRouter

本文依旧运用测试驱动开发的模式进行理顺思路。。。。。。Begin。。。。
vue

  1. 根据不一样的路由渲染三个不一样的组件(Home,News,About)
let Home = () => <div>首页</div>;
let News = () => <div>新闻</div>;
let About = () => <div>关于咱们</div>;
复制代码

渲染方法:react

ReactDOM.render(
    <Router>
        <Route path='/home' component={Home}></Route>
        <Route path='/news' component={News}></Route>
        <Route path='/about component={About}></Route> </Router>, document.querySelector('#app')
)

复制代码

经过如下传递参数的方法,观察获得有用的参数(图1)git

let Home = (props,context)=> {
    console.log(props)
    return  <div>首页</div>;
} 
复制代码

提取最主要的参数:

{
  history:{
    push()
  },
  location:{pathname:'/home'},
  match{
    params:{},
    path:'/home',
    url:'/home'
  }
}
复制代码

编写父组件HashRouter.js,以及单条路由Route.js
HashRouter.js:
在组件挂载的时候,监听hashchange事件,即从新渲染页面github

componentWillMount(){
    window.location.hash = window.location.hash || '/';
    let render = ()=>{
        this.setState({});
    }
    window.addEventListener('hashchange',render);
}
复制代码

经过上下文context进行父子组件之间的通讯,传递location.pathname值vue-router

static childContextTypes = {
    location:PropTypes.object
}
constructor(props){
    super(props);
    this.state = {};
}
getChildContext(){
    return {
        location:{pathname:window.location.hash.slice(1)||'/'}
    }
}
复制代码

HashRouter具体render的对象为子组件,自己并无东西须要render。express

render(){
    return this.props.children;
}
复制代码

Route.js:
获取context中的pathname,跟组件传进来的path进行比较,看是否相等,相等则渲染传递进来的props中的componentnpm

static contextTypes = {
    location: PropTypes.object
}
render(){
    let {path,component:Component} = this.props;
    let {location:{pathname}} = this.context;
    if(path == pathname || pathname.startsWith(path)){
        return <Component location={this.context.location} history={this.context.history}/>;
    }
    return null;
}
复制代码

到此经过浏览器改变输入的hash值,就能够切换到不一样的组件,显示不一样的内容。
2. 经过导航的形式,点击页面的导航条,切换到不一样的页面(Link组件) 本质为a标签。点击经过上下文的history改变hash。redux

HashRouter:
static childContextTypes = {
    location:PropTypes.object
}
getChildContext(){
    return {
        location:{pathname:window.location.hash.slice(1)||'/'},
        history:{
            push(path){
                window.location.hash = path;
            }
        }
    }
}
复制代码

Link.js后端

import React,{Component} from 'react';
import ProTypes from 'prop-types';
export default class Link extends Component{
    static contextTypes = {
        history:ProTypes.object
    }
    render(){
        return (
            // <a href={"#"+this.props.to}>{this.props.children}</a>
            <a onClick={()=>this.context.history.push(this.props.to)}>{this.props.children}</a>
        )
    }
}
复制代码

调用:

<ul className='nav navbar-nav'>
    <li>
        <Link to='/home'>首页</Link>
    </li>
    <li>
        <Link to='/news'>新闻管理</Link>
    </li>
    <li>
        <Link to='/about'>关于咱们</Link>
    </li>
</ul>
复制代码
  1. 二级路由
    咱们建立一个新闻管理的类News.js,在这里进行二级路由的分发,有一个新闻列表(路由为/news/list,组件为NewsList),和一个添加新闻(路由为/news/add,组件为NewsAdd),点击添加新闻和新闻列表 能够跳转到相对应的路由 此项功能经过以前的实现是支持的,无须对咱们本身的HashRouter等进行改写

  2. 路径参数实现之params
    express中,vue中都有相似于'/news/datail/:id'这样的路径,后面的id是动态匹配的,即为路径参数。并且类似的是这些实现都用到了path-to-regxp这个库。这里咱们也重点使用这个库实现路径参数的功能。 在News.js中添加一条路由信息,能够跳转到NewsDetail详情页。

<Route path='/news/datail/:id' component={NewsDetail}></Route>
复制代码

而后在Route.js添加constructor,经过path-to-regexp获取到正则匹配路径信息,而且修改render中路径匹配的方法以下:

constructor(props) {
    super(props);
    let { path } = props;
    this.keys = [];
    this.regxp = pathToRegexp(path, this.keys, { end: false });
    this.keys = this.keys.map(key => key.name);
}
let { location } = this.context;
let result = location.pathname.match(this.regxp);
let props = {
    location,
    history: this.context.history
}
if (result) {
    let [url, ...values] = result;
    props.match = {
        url,
        path,
        params: this.keys.reduce((memo, key, idx) => {
            memo[key] = values[idx];
            return memo;
        }, {})
    }
    return <Component {...props}></Component>
} else {
    return null;
}
复制代码

上述props.match是很重要的一部,拿到match的信息 图1中也有显示

  1. Switch组件 有的时候在咱们写路由信息的时候,会手误写成两个,好比
<Route path='/home' component={Home}></Route>
<Route path='/home' component={Home}></Route>
<Route path='/news' component={News}></Route>
<Route path='/about' component={About}></Route>
复制代码

这里有两个/home,那么页面就会显示两次,这时候咱们须要些一个Switch组件,套在最外层,那么原理就是依次匹配,匹配到了直接返回,再也不往下匹配。由此得出Switch.js的逻辑:

<Switch>
    <Route path='/home' component={Home}></Route>
    <Route path='/home' component={Home}></Route>
    <Route path='/news' component={News}></Route>
    <Route path='/about' component={About}></Route>
</Switch>
export default class Switch extends Component {
    static contextTypes = {
        location: ProTypes.object
    }
    render() {
        let { pathname } = this.context.location;
        let children = this.props.children;
        for (let i = 0; i < children.length; i++) {
            let child = children[i];
            let { path } = child.props;
            if (pathToRegexp(path, [], { end: false }).test(pathname)) {
                return child;
            }
        }
        return null;
    }
}
复制代码

小结

未完待续。。。。

相关文章
相关标签/搜索