React-router is a library that we use a lot during development. Without touching the source code, we might be confused about the value received by the props as the default script. This article is for you.

Q:HashRouterandBrowserRouterSimilarities and differences?

A: The same thing is that the base layer relies on the Router component of the React-Router. In both cases, the history object is created through the history library and passed to the Router component as a parameter.

import { createHashHistory as createHistory } from "history";
import { Router } from "react-router";

class HashRouter extends React.Component {
  history = createHistory(this.props);

  render() {
    return <Router history={this.history} children={this.props.children} />; }}Copy the code
import { Router } from "react-router";
import { createBrowserHistory as createHistory } from "history";

class BrowserRouter extends React.Component {
  history = createHistory(this.props);

  render() {
    return <Router history={this.history} children={this.props.children} />; }}Copy the code

As you can see, the difference is that the history object created by calling different methods in the history library, and not only that, the props exposed are different too:

HashRouter.propTypes = {
  basename: PropTypes.string,
  children: PropTypes.node,
  getUserConfirmation: PropTypes.func,
  hashType: PropTypes.oneOf(["hashbang"."noslash"."slash"])};Copy the code
BrowserRouter.propTypes = {
  basename: PropTypes.string,
  children: PropTypes.node,
  forceRefresh: PropTypes.bool,
  getUserConfirmation: PropTypes.func,
  keyLength: PropTypes.number
};
Copy the code

Q: Why does the Route component props get the History, location, and match objects?

A: The Router uses the context, creates a Provider, and passes location, History, match, and staticContext to the Consumer. Route, being the Consumer of the context, accepts arguments.

// Router.js
import React from "react";
import HistoryContext from './HistoryContext'
import RouterContext from './RouterContext'

class Router extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      location: props.history.location
    };
    
    // Listen for route changes. A 
      
        component changes routes during the mount process.
      
    // To prevent location loss and page jitter (setState)
    // We need to put the location change into memory, wait for the page mount, then setState
    this._isMounted = false;
    this._pendingLocation = null;

    if(! props.staticContext) {this.unlisten = props.history.listen(location= > {
        // If the page is successfully mounted, directly setState;
        // if the page still mounts, wait until the page mounts and then setState
        if (this._isMounted) {
          this.setState({ location });
        } else {
          this._pendingLocation = location; }}); }}componentDidMount() {
    this._isMounted = true;

    if (this._pendingLocation) {
      this.setState({ location: this._pendingLocation }); }}componentWillUnmount() {
    if (this.unlisten) {
      this.unlisten();
      this._isMounted = false;
      this._pendingLocation = null; }}render() {
    return (
      <RouterContext.Provider
        value={{
          history: this.props.history.location: this.state.location.match: Router.computeRootMatch(this.state.location.pathname),
          staticContext: this.props.staticContext
        }}
      >
        <HistoryContext.Provider
          children={this.props.children || null}
          value={this.props.history}
        />
      </RouterContext.Provider>); }}// Route.js
import React from "react";

class Route extends React.Component {
  render() {
    return (
      <RouterContext.Consumer>
        {context => {
          
          const location = this.props.location || context.location;

          // <Switch>The component will pass in the computedMatch field const match = this.props.computedMatch? This.props.com putedMatch: (// matchPath: {path, URL, isExact, params} is returned if the route is a perfect match. This line is the key to controlling the presentation of the routing component this.props. Path? matchPath(location.pathname, this.props) : context.match ); const props = { ... context, location, match }; let { children, component, render } = this.props; if (Array.isArray(children) && React.Children.count(children) === 0) { children = null; } return (<RouterContext.Provider value={props}>
              {
                props.match ? (
                  children ? (
                    typeof children === "function" ? children(props) : children
                  ) : (
                    component ? React.createElement(component, props) : (render ? render(props) : null)
                  )
                ) : (
                  typeof children === "function" ? children(props) : null
                )
              }
            </RouterContext.Provider>
          );
        }}
      </RouterContext.Consumer>
    );
  }
}

// createNamedContext.js
const createNamedContext = name => {
  const context = React.createContext();
  context.displayName = name;

  return context;
}

// HistoryContext.js
const HistoryContext = createNamedContext("Router-History")
export default HistoryContext

// RouterContext.js
const RouterContext = createNamedContext("Router")
export default RouterContext
Copy the code

Children > Component > render = children > Component > render

Q: How does the Switch component ensure that it renders the first routing component

A: Iterate through the sub-components, extract the first routing component that meets the PathName, clone it, and display it.

import React from "react";

class Switch extends React.Component {
  render() {
    return (
      <RouterContext.Consumer>
        {context => {
          const location = this.props.location || context.location;

          let element, match;

          React.Children.forEach(this.props.children, child => {
            if (match == null && React.isValidElement(child)) {
              element = child;

              const path = child.props.path || child.props.from;
              match = path ? matchPath(location.pathname, { ...child.props, path }) : context.match;
            }
          });

          return match
            ? React.cloneElement(element, { location, computedMatch: match })
            : null;
        }}
      </RouterContext.Consumer>); }}Copy the code

Q: How does the high-level component withRouter pass location and so on into a normal component

A: The withRouter internally subscribes to the consumer of the context and passes the value of the context as props to the original component.

import React from "react";
import hoistStatics from "hoist-non-react-statics";

function withRouter(Component) {
  const C = props= > {
    const{ wrappedComponentRef, ... remainingProps } = props;return (
      <RouterContext.Consumer>
        {context => {
          return (
            <Component
              {. remainingProps}
              {. context}
              ref={wrappedComponentRef}
            />
          );
        }}
      </RouterContext.Consumer>
    );
  };

  C.WrappedComponent = Component;

  // Copy non-react static methods on Component to prevent static methods from being lost
  return hoistStatics(C, Component);
}
Copy the code

Q: What are the built-in hooks of the router?

A: Hook is a wrapper around the useContext Hook that retrieves the value of the context. Hooks are classified into four types: useHistory, useLocation, useParams, and useRouteMatch

import { useContext } from 'react'
import RouterContext from "./RouterContext"
import HistoryContext from "./HistoryContext"

export function useHistory() {
  return useContext(HistoryContext)
}

export function useLocation() {
  return useContext(RouterContext).location
}

export function useParams() {
  const match = useContext(RouterContext).match
  return match ? match.params : {}
}

export function useRouteMatch(path) {
  const location = useLocation();
  const match = useContext(RouterContext).match
  return path ? matchPath(location.pathname, path) : match
}
Copy the code

Related links:

Hoist-non-react-statics:zhuanlan.zhihu.com/p/36178509 React. Children: segmentfault.com/a/119000001…