简略版本的React-Router

React-Router须要把握的外围知识点是ContextReact的上下文
用法:

import React from 'react';// Provider 供应者// Consumer 消费者 let {Provider, Consumer} = React.createContext();export {  Provider,  Consumer}

Provider组件是数据的提供者,会将value属性提供给Consumer消费者组件。

Provider组件的用法

import React from 'react';export default class HashRouter extends React.Component {    render() {        return (            <Provider value={{a:1}}>                this.props.children            </Provider>        )    }}

Consumer组件的用法

Consumer组件的内容是一个办法,办法承受一个参数,即是Provider提供的数据。

import React from 'react';export default class Route extends React.Component {    render() {        <Consumer>            {state => {                // 这里的state就是Provider提供的数据                console.log(state);            }}        </Consumer>    }}

路由实现思维

创立一个context.js的文件创建一个Context类,并导出Provider以及Consumer组件,创立一个HashRouter的组件,目标是 通过hashchange的事件来监听location.hash的变动。并用于包裹SwitchRoute以及Redirect组件,像子组件提供数据。Switch组件的目标是为了让 门路匹配只匹配到一个,Route组件用于定义路由。Redirect组件是在未匹配到路由的时候,进行重定向操作。应用Link组件来进行导航,Link组件内通过Consumer来生产HashRouter传递过去的数据,调用HashRouter通过Provider上下文提供一个push办法来扭转HashRouterstate属性达到从新渲染的目标。

组件剖析

react-router-dom/context.js

context.js这个文件就是创立一个上下文环境,用以在HashRouter组件中通过Provider来提供属性,而后在其余组件中通过Consumer来生产所提供的数据。代码如下:

import React from 'react';// Provider 供应者// Consumer 消费者 let {Provider, Consumer} = React.createContext();export {  Provider,  Consumer}

react-router-dom/index.js

ECMASCRIPT6 MODULE中,应用import导入模块的时候,会默认去找导入目录上面的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}

react-router-dom/HashRouter.js

HashRouter组件作为最外层的根组件,用来解决路由变动,监听到hash变动扭转本身的state,而后从新渲染组件。在HashRouter实例化后就会监听hashchange事件,在hash扭转后扭转stateHashRouter组件外面应用Provider来包裹This.props.children,这样子子组件就能够拜访到通过Provider提供的上下文了。在HashRouter组件里通过Provider组件来提供两个属性,locationhistory属性。location属性记录的是以后的window.location对象,也是state外面的location字段;history字段提供一个push办法,push办法用于设置window.location.hash的值来实现路由跳转的性能。代码如下:

import React from 'react';import {Provider} from './context';class HashRouter extends React.Component {    constructor(props) {        super(props);        this.state = {            location: {                pathname: window.location.hash.slice(1) || '/'            },        }    }    componentDidMount() {        // 默认hash没有时候跳转到/        window.location.hash = window.location.hash || '/';        // 监听hash值变动,从新设置状态        window.addEventListener('hashchange', ()=> {            this.setState({                location: {                    ...this.state.location,                    pathname: window.location.hash.slice(1) || '/'                }            })        })    }    render() {        const value = {            location: this.state.location,            history: {                push(to) {                    window.location.hash = to;                }            }        }        return(            <Provider value={value}>                {this.props.children}            </Provider>        )    }}export default HashRouter;

react-router-dom/Switch.js

当存在Redirect组件时,任何路由都将跳转到其配置的门路去,为了解决这个问题,创立了一个一个Switch组件, Switch会遍历所有的this.props.children,将childprops上的path字段用来跟从Consumer组件获取到的location外面的pathname进行正则匹配,若匹配胜利,则返回该组件。代码如下:

import React from 'react';import {Consumer} from "./context";import pathToRegexp from "path-to-regexp";/** * 只匹配一个 */export default class Switch extends React.Component {    render() {        return (            <Consumer>                {                    state => {                        let pathname = state.location.pathname;                        let children = this.props.children;                        for (let i =0; i < children.length; i++) {                            const child = children[i];                            // 可能没有path属性,比方Redirect组件                            let path = child.props.path || '';                            const reg = pathToRegexp(path, [], {end: false});                            // 匹配胜利                            if (reg.test(pathname)) {                                return child;                            }                        }                    }                }            </Consumer>        )    }}

react-router-dom/Route.js

Route组件是用于申明路由,承受三个props属性:

  • path: 这个字段指定路由的门路
  • component: 这个字段用于指定路由所对应的组件
  • exact: 是否严格匹配,严格匹配是指门路以path结尾。例如:能够匹配到/a然而匹配不到/a/b, 如果是非严格匹配的话则能够匹配到。

Route组件通过本身的props获取到path属性,依据path字段来应用pathToRegexp库来创立一个正则来匹配从Consumer生产到的location.pathname属性,如果匹配到了,就返回props字段外面的component所申明的组件。pathToRegexp库承受一个门路、一个数组和其余配置,能够申明一个数组传递进去,用于存储路由携带的动静参数,例如/a/:id/:name,应用pathToRegexp`库的话,传递的数组参数会失去一个如下的值:

[{    name: 'id',}, {    name: 'name'}]

能够应用数组的map办法遍历一下数组就能够通过name属性获取到传入参数的键的一个数组,此时再应用pathname字段用match办法去匹配通过pathToRegexp库所生成的正则,就能够获取到动静参数的值。应用reduce办法遍历寄存参数建的数组,由此来拼装出一个参数对象。最初将从Consumer获取的数据以及拼装好的params参数数据通过props传递给Component。代码如下:

import React from 'react';import pathToRegexp from "path-to-regexp";import {Consumer} from './context';class Route extends React.Component {    constructor(props) {        super(props);        this.state = {}    }    render() {        return (            <Consumer>                {(state) => {                    // path是Route中配置的。                    const {path, component: Component, exact = false} = this.props;                    // pathName是location中的                    let pathname = state.location.pathname;                    // 优化计划, 依据path实现一个正则                    let keys = []                    let reg = pathToRegexp(path, keys, {end: exact});                    keys = keys.map(item => item.name); // 键                    let res = pathname.match(reg);                    let [url, ...values] = res || []; // 值                    // if (pathname === path) {                    let props = {                        location: state.location,                        history: state.history,                        match: {                            params: keys.reduce((obj, current, idx) => {                                obj[current] = values[idx];                                return obj;                            }, {})                        }                    }                    if (res) {                        return <Component {...props}/>                    }                    return null                }}            </Consumer>        );    }}export default Route;

react-router-dom/Link.js

Link组件用来路由导航。Link承受一个toprops属性,用于申明跳转到哪个门路,Link同理能够通过Consumer来生产到从HashRouter传递过去的数据,其中就蕴含history.push这个办法,在组件上绑定一个点击事件,点击后调用history.push这个办法就实现了设置window.location.hash的性能,由此登程hashchange事件,而后批改了HashRouterstate,从新渲染组件。代码如下:

import React from 'react';import {Consumer} from "./context";export default class Link extends React.Component {    render(){        return (            <Consumer>                {                    state => {                        return <button onClick = {                            (e) => {                                e.preventDefault();                                state.history.push(this.props.to)                            }                        }>{this.props.children}</button>                    }                }            </Consumer>        )    }}

react-router-dom/Redirect.js

Redirect是一个重定向组件,当后面的Route组件没有找到匹配的路由的时候,会命中Redirect组件进行重定向,实质就是通过Consumer来调用HashRouter外面的history.push办法。代码如下:

import React from 'react';import {Consumer} from "./context";export default class Redirect extends React.Component {    render() {        return (            <Consumer>                {state => {                    // 重定向就是匹配不到后间接跳转到redirect中的to门路                    state.history.push(this.props.to);                    return null;                }}            </Consumer>        )    }}

demo例子

App.jsx

<div className="App">      <Router>          <>              <div>                  <Link to={"/home"}>主页</Link>                  <Link to={"/profile"}>配置</Link>                  <Link to={"/user"}>用户</Link>              </div>              <div>                  {/*                    exact 严格匹配                    路由/home/123不会加载/home的组件                  */}                  <Switch>                      <Route path="/home" exact={true} component={Home} />                      <Route path="/home/123" component={Home12} />                      <Route path="/profile" component={Profile} />                      <Route path="/user" component={User} />                      <Redirect to="/home" />                  </Switch>              </div>          </>      </Router>    </div>

二级路由

User.js

import React from 'react';import {Link, Route} from "../react-router-dom";import UserAdd from "./UserAdd";import UserList from "./UserList";import UserDetail from "./UserDetail";class User extends React.Component {  constructor(props) {    super(props);    this.state = {  }  }  render() {     return (       <div style={{          display: "flex",      }}>          <nav>              <li><Link to={"/user/add"}>增加用户</Link></li>              <li><Link to={"/user/list"}>用户列表</Link></li>          </nav>          <div style={{              flexShrink: 0          }}>              <Route path={"/user/add"} component={UserAdd} />              <Route path={"/user/list"} component={UserList} />              <Route path={"/user/detail/:id"} exact={true} component={UserDetail} />          </div>      </div>     );  }} export default User;

手动调用办法实现路由跳转

UserAdd.js

import React from 'react';export default class UserAdd extends React.Component {    constructor(props){        super(props);        this.nameInput = React.createRef();    }    handleSubmit = (e) => {        e.preventDefault();        console.log(this.nameInput.current.value);        this.props.history.push('/user/list');    }    render() {        console.log(this.props);        return (            <div>                <form onSubmit={this.handleSubmit}>                    <input ref={this.nameInput} name={"name"} placeholder={"请输出姓名"} type={"text"}/>                    <button type={"submit"}>提交</button>                </form>            </div>        )    }}

获取参数

UserDetail.js

import React from 'react';export default class UserDetail extends React.Component {    render() {        console.log(this.props )        return (            <div>                this is detail component {this.props.match.params.id}            </div>        )    }}