简单版本的React-Router

Posted aren945

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了简单版本的React-Router相关的知识,希望对你有一定的参考价值。

简单版本的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>
        )
    }
}

以上是关于简单版本的React-Router的主要内容,如果未能解决你的问题,请参考以下文章

react-router的简单使用

react-router 源码阅读

react-router v6新特性总结

react-router v6新特性总结

react-router3

关于react-router最新版本的使用