关于前端:简单版本的ReactRouter

54次阅读

共计 7085 个字符,预计需要花费 18 分钟才能阅读完成。

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

正文完
 0