In this episode I decided to copy a Hash based implementation of the React routing library, but since browserRouter is easy I’ll copy it along.

  • 1, I will implement the file classification export, forgive me silently installed a big (age) guy (do not install is old).
  • react-router-dom/index.js
import HashRouter from './HashRouter';
import Route from './Route';
import Link from './Link';
Import Switch from './Switch';
import Redirect from './Redirect';
import NavLink from './NavLink';
import WithRouter from './WithRouter';
import Prompt from './Prompt';import BrowserRouter from './BrowserRouter';

export {
    HashRouter,
    Route,
    Link,
    Swicth,
    Redirect,
    NavLink,
    WithRouter,
    Prompt,
    BrowserRouter
}
Copy the code
  • So let’s do the first stepHashRouter

In fact, a HashRouter is just a container and does not have a DOM structure. Its job is to render its child components and pass data to the underlying components, exp: Location.

  • react-router-dom/HashRouter.js
<! Create and export a HashRouter file -->import React from 'react';
import RouterContext from './RouterContext';
export default class HashRouter extends React.Component {
    state = {
        location : {
            // Remove the hash sign, hash is a hash
            pathname: window.location.hash.slice(1) 
        }
    }
    componentDidMount(){
        // Listen for the haschange event, when the hashchange event is triggered to change the current state and then synchronize the hash value
        window.addEventListener('hashchange', () = > {
            this.setState({ ... this.state.location,pathname: window.location.hash.slice(1) | |'/'.state: this.locationState
            })
        });
        window.location.hash = window.location.hash || '/';
    }
    render() {
        let self = this;
        let history = {
            location: this.state.location,
            push(to) {
                if(typeof to === 'object') { // It is possible that the user passed an object containing a path and state
                    let {pathname, state} = to;
                    that.locationState = state;
                    window.location.hash = pathname;
                } else { // The string to pass
                    window.location.hash = to
                }
            }
            block(prompt){
                history.prompt = prompt
            }
            unblock() {
                history.prompt = null; }}let routerValue = {
            location: that.state.location,
            history
        }
        return (
            <RouterContext.Provider value= {routerValue}>
                {this.props.chilren}
            </RouterContext.Provider>)}}Copy the code

Here we will implement the Route component, which most often represents a routing rule. State, location, and PathName in a hashRouter are passed through the context.

  • react-router-dom/Route.js
import React from 'react';
import RouterContext from './RouterContext';
import pathToRegexp from ' path-to-regexp';
export default class Route extends React.Component {
    static contextType = RouterContext; // this.context.location.pathname
    // Component is the component property of the route component, which is the component of the route. I rename it RouteComponent.
    // Path is the path uploaded by the routing component for matching
    // Excat indicates whether the path on the route matches exactly with the path-to-regexp parameter.
    // Components can be rendered in three ways: by passing in a render function, passing in a children attribute that is executed as a function, and rendering directly from the matching component.
    render() {
        let { path='/'.component: RouteComponent, excat= false, render, children } = this.props;
        let path = typeof path === 'object' ? path.pathname : path;
        let pathname = this.context.location.pathname;
        let paramNames = [];
        let regexp = pathToRegexp(path, paramNames, {end: exact});
        paramNames = paramNames.map(p= >p.name);
        let matched = pathname.match(regexp);
        let routeProps = {
            location: this.context.location,
            history: this.context.history
        };
        if(matched) {
            let [url, ...values] = matched;
            let params = values.reduce((memo, cur, index) = > {
                memo[paramNames[index]] = cur;
                return memo;
            }, {})
            let match - {
                url,
                path,
                isExact: pathname === url,
                params
            }
            routerProps.match = match;
            if(RouteComponent) {
                return <RouteCompoponent {. routerProps} / >} else if(render) { return render(routerProps); } else if(children) { children(routerProps); } else { return null; } } else { if(children) { return children(routerProps); } else { return null; }}}} // The presence of the match attribute is largely related to whether the component is rendered by the route. // isExact indicates whether the pathname and URL are exactly the sameCopy the code
  • react-router-dom/RouterContext.js
RouterContext = RouterContext; RouterContext = RouterContext
// This is a simple one
import React from 'react';
export default const context = React.createContext();
// It's a little too simple
Copy the code
  • Next I will implement Link, in fact, a careful analysis, in fact, this thing is just a label, not so magical. Router-link is similar to Vue’s router-link. I think it’s good to use, technology is mutually reinforcing.
  • react-router-dom/Link.js
<! Functional programming-->import React from 'react';
import RouterContext from 'RouterContext';
// Be careful not to use href and assign it to a specific path, because it will bypass the interception of the route and take the attributes of the A tag
export default function Link(props) {
    return (
        <RouterContext.Consumer>
            {
                routerValue => {
                    <a {. props}
                        onClick ={() = > {
                                routerValue.history.push(props.to)
                            }
                        }
                    >
                        {
                            prpos.children
                        }
                    </a>}}</RouterContext.Consumer>)}Copy the code
  • Let me implement the Switch. His job is simply to match the child components, rendering only the first child that matches. andvuetherouter-viewVery like.
  • react-router-dom/Switch.js
import React, {useContext} from 'react';import RouterContext from 'RouterContext';
import pathToRegexp from 'path-to-regexp';// Here I've introduced a new thing called useContext which is one of the hooks.
// useContext is the third way to get context objects
// static contextType (used in class)
// Consumer(used in functions)
// ReactHooks useContext can also be used to get context objects

export default function (props) {
    let routerContext = useContext(RouterContext);
    let children = props.children;
    children = Array.isArray(children) ? children : [children];
    let pathname = routerContext.location.pathname;// Retrieves the current path from the context
    for(let i = 0; i < children.length; i++) {
    // Remember that child is the React element or the virtual DOM is not a component
    // Equivalent to 'react. createElement(Route,{exact,path,component})',
    // `{type:Route,props:{exact,path,component}}`
        let child = children[i];
        let { path = '/', component, exact = false} = child.props;
        <! Path-to-regexp is a regex library used for routing.
        let regexp = pathToRegexp(path, [], {end: exact});
        <! -- Use current path to match -->
        let matched = pathname.match(regexp);
        <! Direct render subcomponent is matched -->if(matched) { return child; }}<! Return null--> 
    return null
}
Copy the code
  • Redirect component, which realizes automatic redirect operation when the corresponding component is not matched through the URL.
  • react-router-dom/Redirect.js
import React, {useContext} from 'react';
import RouterContext from './RouterContext';
export default function (props) {
    let routerContext = useContext(RouterContext);
    if(! props.from || props.from === routerContext.location.pathname) { routerContext.history.push(props.to); }return null;
}
Copy the code
  • Components highlighted above the navigation bar.
  • react-router-dom/NavLink.js
  • react-router-dom/NavLink.css
.acitve {
    color: # 425658;
    background: #eeeddd;
}
Copy the code
import React from 'react';
import './NavLink.css'
import { Route, Redirect, Link } from './index';
export default function (props) {
    let { to, exact, children } = props;
    return (
        <Route 
            path={to}
            exact={exacr}
            children={
                routerProps= > {
                    <Link
                        className={
                            routerProps.match? 'active' :' 'to={to}} >
                        {children}    
                    </Link>
                }
            }>
         
        </Route>)}Copy the code
  • Prompt blocks jump components and has no DOM structure
  • react-router-dom/Prompt.js
import React, {useContext} from 'react';
import RouterContext from './RouterContext';
export default function (props) {
    let routerContext = useContext(RouterContext);
    let {bool, msg} = props;
    if(bool) {
        routerContext.history.block(msg);
    } else {
        routerContext.history.unblock()
    }
    return null
}
Copy the code
  • Write two high-level components
<! -- 1And withContext. Js - >import React from 'react';
import { Route } './index';
export default function (OldComponent) {
    reutnr props => {
        <Route>
            render={
                routerProps =><OldComponent
                    {. props}
                    {. routerProps} / >
            }
        </Route>}}export default function (OldComponent) {
    return (
        <RouterContext.Consumer>
            {
                contextValue => (
                    <div>
                        <OldComponent/>
                    </div>)}</RouterContext.Consumer>)}Copy the code
  • Implement BrowserRouter as well, by the way
  • react-router-dom/BrowserRouter
import React, { useState, useEffect } from 'react';
import RouterContext from "./RouterContext.js";
export default function BrowserRouter(props) {
    let [currentState, setCurrentState] = useState({ location: { pathname: window.location.pathname } });
    useEffect((a)= > {
        window.onpushstate = (state, pathname) = > {
            setCurrentState({
                location: {
                    ...currentState.location,
                    pathname,
                    state
                }
            });
        }
        window.onpopstate = (event) = > {
            setCurrentState({
                location: {
                    ...currentState.location,
                    pathname: window.location.pathname,
                    state: event.state } }); }} []);const globalHistory = window.history;
    let history = {
        location: currentState.location,
        push(to) {
            if (history.prompt) {
                let target = typeof to === 'string' ? { pathname: to } : to;
                let yes = window.confirm(history.prompt(target));
                if(! yes)return;
            }
            if (typeof to === 'object') {{pathname,state}
                let { pathname, state } = to;
                globalHistory.pushState(state, null, pathname);
            } else {// It's just a string
                globalHistory.pushState(null.null, to);
            }
        },
        block(prompt) {
            history.prompt = prompt;
        },
        unblock() {
            history.prompt = null; }}let routerValue = {
        location: currentState.location,
        history
    }
    return (
        <RouterContext.Provider value={routerValue}>
            {props.children}
        </RouterContext.Provider>)}Copy the code