Analyze the principle of react router V5 from the source code

This article will analyze the source code of the remaining components in the react router

<Redirect>

Like other routing components, < redirect > uses < routercontext Consumer > receive routing data;

Define prop types for < redirect >

Redirect.propTypes = {
  push: PropTypes.bool,
  from: PropTypes.string,
  to: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired
};

Rendering logic of < redirect >

First, determine whether the jump mode of < redirect > is push or replace through the incoming push:

// push is props push, the default is false
const method = push ? history.push : history.replace;

Next, make sure that the location: createLocation of the jump is the method of the history library, and a location object will be created according to the passed parameters:

// to is props to, computedmatch is props computedMatch
const location = createLocation(
  computedMatch
  ? typeof to === "string"
  ? generatePath(to, computedMatch.params)
  : {
    ...to,
    pathname: generatePath(to.pathname, computedMatch.params)
  }
  : to
);

Note:

  1. When < redirect > is used as a sub component of < Switch > and matched, < Switch > will pass the computedMatch calculated by matching to < redirect >
  2. generatePath is an api provided by react router, which is used to combine path and parameters to generate a pathname

Next is the implementation of < redirect > jump logic:

<Lifecycle
  onMount={() => {
    method(location);
  }}
  onUpdate={(self, prevProps) => {
    const prevLocation = createLocation(prevProps.to);
    if (
      !locationsAreEqual(prevLocation, {
        ...location,
        key: prevLocation.key
      })
    ) {
      method(location);
    }
  }}
  to={to}
/>

The < lifecycle > component structure is very simple. It supports three methods: onMount, onUpdate and onUnmount, which represent componentdidmount, componentdidupdate and componentwillunmount respectively;

Therefore, the actions triggered by < redirect > using Lifecycle are as follows:

  1. < redirect > push/replace jump in the life cycle of componentDidMount;
  2. In the componentDidUpdate life cycle, use the locationsarequal method of the history library to compare whether the previous location is the same as the new location. If the location is different, execute the push/replace jump event;
// LifeCycle.js
import React from "react";

class Lifecycle extends React.Component {
  componentDidMount() {
    if (this.props.onMount) this.props.onMount.call(this, this);
  }

  componentDidUpdate(prevProps) {
    if (this.props.onUpdate) this.props.onUpdate.call(this, this, prevProps);
  }

  componentWillUnmount() {
    if (this.props.onUnmount) this.props.onUnmount.call(this, this);
  }

  render() {
    return null;
  }
}

export default Lifecycle;

<Link>

< link > realizes the route jump in the react router;

Define prop types for < link >

const toType = PropTypes.oneOfType([
  PropTypes.string,
  PropTypes.object,
  PropTypes.func
]);
const refType = PropTypes.oneOfType([
  PropTypes.string,
  PropTypes.func,
  PropTypes.shape({ current: PropTypes.any })
]);

Link.displayName = "Link";

Link.propTypes = {
  innerRef: refType,
  onClick: PropTypes.func,
  replace: PropTypes.bool,
  target: PropTypes.string,
  to: toType.isRequired
};

In fact, < link > also has a prop: component, but it's not clear why there is no type declaration for the component here;

Rendering logic of < link >

< link > use < routercontext Consumer > receive routing information;

< link > through props To, get the href attribute and declare the props object:

(
    {
    component = LinkAnchor,
    replace,
    to,
    innerRef, // TODO: deprecate
    ...rest
  }
) => {
    //  ...  By processing props To get href
  const props = {
    ...rest,
    href,
    navigate() {
      const location = resolveToLocation(to, context.location);
      const method = replace ? history.replace : history.push;

      method(location);
    }
  };
  
  // ...
}

And inject the props obtained above into the component:

return React.createElement(component, props);

From the source code, we can see that the component here is LinkAnchor by default, so let's read the source code of < LinkAnchor >:

The props structure of LinkAnchor is as follows:

{
  innerRef, // TODO: deprecate
  navigate,
  onClick,
  ...rest
}

Mainly navigate and onClick:

As you can see from the < link > source code, navigator mainly judges the jump type through the passed in replace attribute, and selects history according to the corresponding jump type Replace or history Push for route jump:

navigate() {
  const location = resolveToLocation(to, context.location);
  const method = replace ? history.replace : history.push;

  method(location);
}

onClick is better understood as the click event declaration of the < link > component;

< linkanchor > generates a props through the incoming props and returns a hyperlink injected with props:

let props = {
    // ...
};
return <a {...props} />;

The main function is the onClick of the hyperlink. In the click event, first judge whether there are props onClick, execute immediately if it exists; Then check whether to execute props Navigator's judgment:

Whether to jump or not requires all of the following conditions:

  • event.button === 0: the click event is the left mouse button;
  • ! target || target === "_self": _target does not exist, or_ Target is_ self;
  • ! isModifiedEvent(event): when clicking the event, no other key is pressed at the same time;

    Note: isModifiedEvent is used to judge whether other keys are pressed at the same time when the click event occurs;

if (
  !event.defaultPrevented && // onClick prevented default
  event.button === 0 && // ignore everything but left clicks
  (!target || target === "_self") && // let browser handle "target=_blank" etc.
  !isModifiedEvent(event) // ignore clicks with modifier keys
) {
  // ...
}

When all the above conditions are met, execute the following code:

event.preventDefault();
navigate();

event.preventDefault() prevents the default event of the hyperlink from refreshing the page after clicking < link >;

navigate() uses history Push or history Replace performs route jump, triggers the history listening event declared in < router >, and re renders the routing component!

withRouter

Define prop types of withRouter

wrappedComponentRef enables high-level components to access the ref of its wrapped components;

C.propTypes = {
  wrappedComponentRef: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.func,
    PropTypes.object
  ])
};

Rendering logic of withRouter

withRouter is a high-level component that supports the incoming of a component and the return of a routing component that can access routing data. In essence, it takes the component as < routercontext The sub component of consumer > and inject the routing information of context into the component as props;

const C = props => {
  // ... Return component
  const { wrappedComponentRef, ...remainingProps } = props;

    return (
      <RouterContext.Consumer>
                {context => {
          return (
            <Component
              {...remainingProps}
              {...context}
              ref={wrappedComponentRef}
            />
          );
        }}
      </RouterContext.Consumer>
    );
};

return hoistStatics(C, Component);

Hoiststatistics is a third-party library hoist non react statistics, which is used to solve the problem of losing static of original components in high-order components; At the same time, support the incoming props: wrappedComponentRef, which binds the ref of the original component, so you can access the original component through wrappedComponentRef; It should be noted that there is no ref for functional components. Because there is no instance for functional components, it is not supported to use wrappedComponentRef to access the original components when wrapping functional components with withRouter!

Hooks

React Router ships with a few hooks that let you access the state of the router and perform navigation from inside your components.

Please note: You need to be using React >= 16.8 in order to use any of these hooks!

React router provides some hooks, so that we can get the status of the route in the component and perform navigation; If we need to use these hooks, we need to use react > = 16.8;

The hooks of React router actually use the hooks: useContext provided by React, so that we can access the HistoryContext and the data in RouterContext in the component;

useHistory

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

const useContext = React.useContext;

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

useLocation

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

const useContext = React.useContext;

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

useParams

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

const useContext = React.useContext;

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

useRouteMatch

import React from 'react';
import RouterContext from "./RouterContext.js";
import matchPath from "./matchPath.js";

const useContext = React.useContext;

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

Note:

  • useRouteMatch uses hook: useLocation to obtain location;
  • matchPath is a public api of react router. It supports passing in a pathname and path. If the path matches the pathname, a match object will be returned, and if not, a null will be returned;

ending

This is the end of the series of principle analysis of react router V5 from the source code. In fact, there are some cold components that have not been read from the source code (dig a pit and fill it in when you have time);

Think about it carefully. This is the first time to systematically read a Gaoxing library. This source code reading makes me feel that I have benefited a lot. Compared with the library I wrote, there is a gap of 18000 miles in terms of design and overall packaging (laugh, I still have to work hard;

The author was biased towards vue before, because he began to systematically learn React recently, so he wanted to take advantage of his enthusiasm to dig up some high star libraries of React and see if he could understand some tips or design ideas in React development from the source code, so the purpose is achieved;

It seems that there are many problems in the process of maintaining the ecological database, because there are many problems in the development of this kind of database. In fact, I feel confused about whether there are many good basic tools in the past;

Here is a question:

In react, I can override the props of the component by writing:

const props = {
  title: 'new title'
};
<Component title="Old title" {...props}></Component>

In vue, however, the props of previous components cannot be overwritten with the following wording:

<template>
  <Component title="Old title" v-bind="{title: 'new title'}"></Component>
</template>

Have you seen the vue source code to answer your doubts? Then the next goal is to see the source code of vue!

Tags: Javascript Front-end React source code analysis react-router

Posted by dyip on Tue, 17 May 2022 23:20:24 +0300