1 Functions to be implemented

When we were developing projects with React, it was basically a single page app, so we needed routing. Routing may seem mysterious, but when we briefly simulate its core functionality, we find that it is. This article will introduce the react-router-DOM HashRouter core implementation logic in detail.

The functions implemented in this paper mainly include:

  • HashRouter
  • Route
  • Link
  • MenuLink
  • Switch
  • Redirect

2 Implementation logic

Let’s take a look at what a HashRouter is:

  • HashRouterIt’s a big container, and it controls how it renders itself, and what does it control by, as you can guess from its name, is thatwindow.location.hash.
  • whenHashRouterIt will be on its own when it starts renderingpathnameAttributes with its bellyRoutethepathIt matches, and if it matches, it rendersRoutethecomponentThe corresponding component.
  • LinkHow do you switch routes? It’s very simple, just throughthis.props.history.push(path)To change theHashRouterIn thepathnameProperty, which in turn drivesThe RouteRe-render, match our route again, and finally achieve the switch of the route.

After introducing the simple logic, let’s take a look at how to implement it, as shown in the figure below:

  • HashRouterIt’s an inheritanceReact.ComponentOn this classstateincludinglocationAnd to monitor thehashChange in order to driveRouteComponent re-render, and one morehistoryProperty to switch the routing of the page.
  • The functions to be implemented in this article includeRoute,Link,MenuLink,Switch,Redirect, includingRouteIs that the foundation is the core,MenuLinkAnd some rendering with specific logicRouteOn the basis of.
  • RouteThere are three variables that can be received on a component, includingcomponent,render,children, includingrender,childrenAre both functions,renderIs to render elements according to specific logic,childrenIt’s used for renderingMenuLinkBoth of these functions receive the current routepropsThe return value of the function is the element to render.
  • SwitchThe logic of the implementation is, returnchildrenwithhashThe first child I ever matched.

3 Specific code logic

(1) HashRouter

HashRouter hooks window.loacation.hash to its state and drives the rerendering of the page by changing its state.

import React, {Component} from 'react';
import PropTypes from 'prop-types';

export default class HashRouter extends Component {
    constructor() {
        super(a);this.state = {
            location: {
                pathname: window.location.hash.slice(1) | |'/'.// The hash value of the current page
                state: {}   // The saved state}}; }// Define the variable type of the context
    static childContextTypes = {
        location: PropTypes.object,
        history: PropTypes.object
    }
    
    // Define the context variables
    getChildContext() {
        return {
            location: this.state.location,
            history: {
                push: (path) = > { // Update the window.hash value
                    if (typeof path === 'object') {
                        let {pathname, state} = path;
                        this.setState({
                            location: {
                                ...this.state.location,
                                state // {from: '/profile'}}}, () => {window.location.hash = pathname; })}else {
                        window.location.hash = path;
                    }
                }
            }
        }
    }
    
    render() {
        return this.props.children; // Render the page elements
    }
    
    componentDidMount() {
        window.location.hash = window.location.hash.slice(1) | |'/';
        // Listen for window hash changes and drive the page to refresh
        window.addEventListener('hashchange', () = > {this.setState({
                location: {
                    ...this.state.location,
                    pathname: window.location.hash.slice(1) | |'/'}}); }}})Copy the code

(2) Route

The core rendering logic of Route is to match its path with the hash of the current page. If the path matches, render the corresponding element. If the path does not match, render nothing.

import React, {Component} from 'react';
import PropTypes from 'prop-types';
import pathToRegexp from 'path-to-regexp'

export default class Route extends Component {
    // Define the type of context
    static contextTypes = {
        location: PropTypes.object,
        history: PropTypes.object
    }
    
    render() {
        // Deconstruct the props passed to Route
        let {path, component: Component, render, children} = this.props;
        
        // Deconstruct the properties of the context
        let {location, history} = this.context;
        let props = {
            location,
            history
        };
        
        // Match the passed path to the current hash
        let keys = [];
        let regexp = pathToRegexp(path, keys, {end: false});
        keys = keys.map(key= > key.name);
        
        let result = location.pathname.match(regexp);
        
        if (result) { // A match
            let [url, ...values] = result;
            props.match = {
                path,
                url,
                params: keys.reduce((memo, key, index) = > { // Get the matched parameter
                    memo[key] = values[index];
                    returnmemo; }, {}};if (Component) { // Normal Route
                return <Component {. props} / >; } else if (render) {return render(props); } else if (children) {// MenuLink render return children(props); } else { return null; }} else {// there is no match if (children) {// MenuLink render return children(props); } else { return null; }}}}Copy the code

(3) Redirect

One thing Redirect does is change the state of the HashRouter to drive the rerendering.

import React, {Component} from 'react';
import PropTypes from 'prop-types';

export default class Redirect extends Component {
    // Define the Type of context
    static contextTypes = {
        history: PropTypes.object
    }
    
    componentDidMount() {
        // Jump to the destination route
        this.context.history.push(this.props.to);
    }
    
    render() {
        return null; }}Copy the code

(4) MenuLink

import React, {Component} from 'react';
import Route from "./Route";
import Link from './Link'

export default ({to, children}) => {
    // If a match is found, give the current component an active className
    return <Route path={to} children={props= > (
        <li className={props.match ? "active" :""} >
            <Link to={to}>{children}</Link>
        </li>
    )
    }/>
}
Copy the code

(5) Link

The Link is rendered as an A tag and then given a click event that changes the state of the HashRouter and drives the re-render.

import React, {Component} from 'react';
import PropTypes from 'prop-types';

export default class Link extends Component {
    static contextTypes = {
        history: PropTypes.object
    }
    
    render() {
        return (
            <a onClick={()= > this.context.history.push(this.props.to)}>{this.props.children}</a>)}}Copy the code

(6) Switch

import React, {Component} from 'react';
import PropTypes from 'prop-types';
import pathToRegexp from 'path-to-regexp';

export default class Switch extends Component {
    static contextTypes = {
        location: PropTypes.object
    }
    
    render() {
        let {pathname} = this.context.location;
        
        let children = this.props.children;
        for (let i = 0, l = children.length; i < l; i++) {
            let child = children[i];
            let path = child.props.path;
            
            if (pathToRegexp(path, [], {end: false}).test(pathname)) {
                // Return the first matched element
                returnchild; }}return null}}Copy the code

I’ll write 4 at the end

Ok, so that’s it. Do you have any idea how HashRouter works? This article just posted part of the code, if you need to see the demo can be manually experience oh.

References:

  • react-router