preface

This section describes the core principles of the React-Router. Particularly detailed punctuation is off the table.

Preschool tips

The core of the React-Router is the Route component and the History library developed by the unified author. Let’s take a look at the mysterious world of ßReact-Router.

Already know how to use the skip, directly to the source code analysis to ┗ | ` O ‘| ┛ ao ~ ~

A simple example

Let’s build a simple example. React create-react-app create-react

npx create-react-app my-app
cd my-app
npm start
Copy the code

Install the react – the router – the dom

npm install react-router-dom
Copy the code

React-router and react-router-dom are different from each other.

React-router differs from react-router-dom.

First look at the API provided

import { Switch, Route, Router } from 'react-router';

import { Swtich, Route, BrowserRouter, HashHistory, Link } from 'react-router-dom';
Copy the code

React-router

Provides a core API for routing. Such as Router, Route, Switch, etc., but there is no API for dom operation for Route jump;

React-router-dom

BrowserRouter, Route, Link and other apis can be provided to trigger events to control routes through DOM manipulation.

Link component, which renders an A tag; The BrowserRouter and HashRouter components, the former using pushState and popState events to build routes, and the latter using hash and HashChange events to build routes.

React-router-dom extends the REact-router API for manipulating dom.

Swtich and Route both import and re-export components from the React-Router without any special processing.

The package.json dependency in the react-router-dom has a dependency on the react-router. Therefore, NPM does not need to install the React-router.

  • Install react-router-dom directly from NPM and use its API.

Simple modification

👌, everyone 🔥 should have been installed, right? The next step is to simply modify the contents of the scaffolding. Mainly to get to know your opponent, know yourself and know your opponent.

Modify SRC /app.js

import React from 'react';
import {
  BrowserRouter as Router,
  Switch,
  Route,
  Link,
} from "react-router-dom";

function Home() {
  return (
    <>
      <h1>Home page</h1>
      <Link to="/login">The login</Link>
    </>)}function Login() {
  return (
    <>
      <h1>The login page</h1>
      <Link to="/">Back to the home page</Link>
    </>
  );
}

function App() {
  return (
    <Router>
      <Switch>
        <Route path="/login" component={Login}/>
        <Route path="/" component={Home}/>
      </Switch>
    </Router>
  );
}

export default App;
Copy the code

This completes the simplest example possible.

The core idea of SPA

  • Listening to theURLThe change of the
  • To change somecontextThe value of the
  • Get the corresponding page component
  • renderThe new page

The source code for

BrowserRouter

From the simple example above, we found that there is an outermost buddy called BrowserRouter. Let’s go straight to his Github source.

Link: github.com/ReactTraini…

import React from "react";
import { Router } from "react-router";
import { createBrowserHistory as createHistory } from "history";
import PropTypes from "prop-types";
import warning from "tiny-warning";

/** * The public API for a 
      
        that uses HTML5 history. */
      
class BrowserRouter extends React.Component {
  history = createHistory(this.props);

  render() {
    return <Router history={this.history} children={this.props.children} />; }}if (__DEV__) {
  BrowserRouter.propTypes = {
    basename: PropTypes.string,
    children: PropTypes.node,
    forceRefresh: PropTypes.bool,
    getUserConfirmation: PropTypes.func,
    keyLength: PropTypes.number
  };

  BrowserRouter.prototype.componentDidMount = function() {
    warning(
      !this.props.history,
      "<BrowserRouter> ignores the history prop. To use a custom history, " +
        "use `import { Router }` instead of `import { BrowserRouter as Router }`."
    );
  };
}

export default BrowserRouter;

Copy the code

Throw away some judgment. See again.

import React from "react";
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

He was just a middleman. He was making a difference. (pay them!

BrowserRouter relies on two libraries: History and React-Router. Ok, let’s find out.

react-router

Source link: github.com/ReactTraini…

import HistoryContext from "./HistoryContext.js";
import RouterContext from "./RouterContext.js";
Copy the code

Both of these things are actually very simple, they refer to something called createContext, and the purpose is very simple, so it’s just a normal context being created with a specific name. The source code is as follows. Just a few lines.

// TODO: Replace with React.createContext once we can assume React 16+
import createContext from "mini-create-react-context";

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

  return context;
};

export default createNamedContext;
Copy the code

OK, the point is what we need to focus on now, after we get rid of the context. Let me rearrange this.

Look at the constructors first

The constructor

constructor(props) {
  super(props);

  this.state = {
    location: props.history.location
  };

  // This is a bit of a hack. We have to start listening for location
  // changes here in the constructor in case there are any <Redirect>s
  // on the initial render. If there are, they will replace/push when
  // they mount and since cDM fires in children before parents, we may
  // get a new location before the <Router> is mounted.
  
  // _isMounted Indicates whether the component is loaded successfully
  this._isMounted = false;
  // The component is not loaded yet, but the location changes are present in the _pendingLocation field
  this._pendingLocation = null;

  // No staticContext attribute, indicating either HashRouter or BrowserRouter
  if(! props.staticContext) {this.unlisten = props.history.listen(location= > {
      if (this._isMounted) {
        // After the component is loaded, the location method state will change
        this.setState({ location });
      } else {
        this._pendingLocation = location; }}); }}Copy the code

☞_isMounted and _pendingLocation have two values

Yes Whether to mount them is to be determined

A location is maintained internally, and the default value is history passed in from outside, and I find where it was passed in.

It’s actually BrowserRouter that you saw earlier

import React from "react";
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 default value for history is provided by the history library, which will be covered later.

Take a quick look at the translation of the comments (Google Translate)

This is a bit of a hack. We have to start listening for position changes in the constructor in case there are any in the initial render. If any, they will be replaced/pushed at install time, and since the cDM is triggered in the child before the parent, we may get a new location before the installation.

So what does that mean? Let me just describe it:

Because of the child component rendering before the parent component and the presence of the

, it is possible to listen on history.location during the

componentDidMount life cycle before the listener event is registered. The history.location has been changed several times due to the

, so we need to register the listener event from

constructor



Take it back, go into if and continue.

this.unlisten = props.history.listen(location= > {
  if (this._isMounted) {
  	this.setState({ location });
  } else {
  	this._pendingLocation = location; }});Copy the code

The history listen method is called. You can see from the code that you’re listening for history and doing something about it.

componentWillUnmount

So when does this unlisten run?

componentWillUnmount() {
    if (this.unlisten) {
      this.unlisten();
      this._isMounted = false;
      this._pendingLocation = null; }}Copy the code

This is done when the Router component is uninstalled. In other words, the listening for History is disabled.

componentDidUnmount

And then look at conponentDidMount

componentDidMount() {
    this._isMounted = true;

    if (this._pendingLocation) {
      this.setState({ location: this._pendingLocation }); }}Copy the code

Simply change the value of the mount or not, and continue the previous determination in the constructor. Save it if there’s any flash data.

render

Render () {return (<RouterContext.Provider value={{// Render () {return (<RouterContext.Provider value={{// // This.props. History, // this.props. History This.state. location, // path url params isExact match: Router.computeRootMatch(this.state.location.pathname), // Only StaticRouter will pass staticContext. // Both HashRouter and BrowserRouter will be null staticContext: this.props.staticContext }} > <HistoryContext.Provider children={this.props.children || null} value={this.props.history}  /> </RouterContext.Provider> ); }Copy the code

conclusion

The Router is a component that stores data. Save to Context, there are some special cases between judgment, such as child component rendering before parent component, and Redirect situation handling. Listening for history is removed during uninstallation.

The child component, acting as a consumer, can modify the page, jump, and fetch these values.

history

createBrowserHistory

What is returned

There’s been a lot of history, but let’s see who it is

Source link: github.com/ReactTraini…

The createBrowserHistory that we used earlier, it’s actually an object that’s returned, and it’s got some methods in it that we use a lot.

let history: BrowserHistory = {
    get action() {
      return action;
    },
    get location() {
      return location;
    },
    createHref,
    push,
    replace,
    go,
    back() {
      go(-1);
    },
    forward() {
      go(1);
    },
    listen(listener) {
      return listeners.push(listener);
    },
    block(blocker) {
      let unblock = blockers.push(blocker);

      if (blockers.length === 1) {
        window.addEventListener(BeforeUnloadEventType, promptBeforeUnload);
      }

      return function() {
        unblock();

        // Remove the beforeunload listener so the document may
        // still be salvageable in the pagehide event.
        // See https://html.spec.whatwg.org/#unloading-documents
        if(! blockers.length) {window.removeEventListener(BeforeUnloadEventType, promptBeforeUnload); }}; }};return history;
}
Copy the code

Most of the essence is here. Discover how familiar companions are! => Push, replace, go, etc. Some of them are actually provided by Window.

There are some things that you can see directly in the code that you don’t want to explain, like forward and back.

go

For example, the go method mentioned above intercepts part of the source code.

let globalHistory = window.history;
function go(delta: number) {
    globalHistory.go(delta);
  }
Copy the code
createHref

// Return a full URL
export function createPath({
  pathname = '/',
  search = ' ',
  hash = ' '
}: PartialPath) {
  return pathname + search + hash;
}

// Return a URL
function createHref(to: To) {
  	/ / see above
    return typeof to === 'string' ? to : createPath(to);
  }
Copy the code
push

Push source code, with some of the functions that will be used.

// Simply handle the return value.
function getNextLocation(to: To, state: State = null) :Location {
  returnreadOnly<Location>({ ... location, ... (typeof to === 'string' ? parsePath(to) : to),
    state,
    key: createKey()
  });
}

// Make a judgment
function allowTx(action: Action, location: Location, retry: () => void) {
    return (
      If the length is greater than 0, the function is called and the argument is passed. This Blockers will be explored in more detail later! blockers.length || (blockers.call({ action, location, retry }),false)); }// Get state and URL as the name implies
function getHistoryStateAndUrl(
    nextLocation: Location,
    index: number
  ) :HistoryState.string] {
    return[{usr: nextLocation.state,
        key: nextLocation.key,
        idx: index
      },
      // There is a special introduction above
      createHref(nextLocation)
    ];
  }

// Return some information about location
function getIndexAndLocation() :number.Location] {
    let { pathname, search, hash } = window.location;
    let state = globalHistory.state || {};
    return [
      state.idx,
      readOnly<Location>({
        pathname,
        search,
        hash,
        state: state.usr || null.key: state.key || 'default'})]; }// Some of the functions (jumps) inside the listeners are implemented, which are explained in detail later
function applyTx(nextAction: Action) {
    action = nextAction;
    [index, location] = getIndexAndLocation();
    listeners.call({ action, location });
  }


function push(to: To, state? : State) {
  	// Here is an enumeration value
    let nextAction = Action.Push;
  	
    let nextLocation = getNextLocation(to, state);
  	// Just as the name implies, just one more time
    function retry() {
      push(to, state);
    }

    if (allowTx(nextAction, nextLocation, retry)) {
      let [historyState, url] = getHistoryStateAndUrl(nextLocation, index + 1);

      // TODO: Support forced reloading
      // try... catch because iOS limits us to 100 pushState calls :/
      try {
        / / MDN address: https://developer.mozilla.org/zh-CN/docs/Web/API/History/pushState
        globalHistory.pushState(historyState, ' ', url);
      } catch (error) {
        // They are going to lose state here, but there is no real
        // way to warn them about it since the page will refresh...
        / / MDN address: https://developer.mozilla.org/zh-CN/docs/Web/API/Location/assign
        window.location.assign(url); } applyTx(nextAction); }}Copy the code

I have explained this in great detail in the comments, and each function used has an explanation or official URL.

To summarize: a complete flow of history.push

  • callhistory.pushState
    • Error bywindow.location.assignTo deal with
  • To perform alistenersThe function inside

Yes, you are right, it is as simple as that, but there are many functions called in it, I cut them out and explained them one by one, so that I can understand each line of code, so it is quite long, in a nutshell, it is so simple.

Focus on listen and the createBrowserHistory being called

replace

The function used in this, in front of the push have been resolved, you can go to the above to find, there is no further details.

function replace(to: To, state? : State) {
    let nextAction = Action.Replace;
    let nextLocation = getNextLocation(to, state);
    function retry() {
      replace(to, state);
    }

    if (allowTx(nextAction, nextLocation, retry)) {
      let [historyState, url] = getHistoryStateAndUrl(nextLocation, index);

      // TODO: Support forced reloading
      globalHistory.replaceState(historyState, ' ', url); applyTx(nextAction); }}Copy the code
listen

The listen returned in history is a function. This function was found in the react-router source code and is used for constructor and uninstallation.

listen(listener) {
      return listeners.push(listener);
    },
Copy the code

Look carefully at what the listeners are doing.

createEvents

Blockers after this function are also used

let listeners = createEvents<Listener>();

function createEvents<F extends Function> () :Events<F> {
  let handlers: F[] = [];

  return {
    get length() {
      return handlers.length;
    },
    push(fn: F) {
      handlers.push(fn);
      return function() {
        handlers = handlers.filter(handler= >handler ! == fn); }; },call(arg) {
      handlers.forEach(fn= >fn && fn(arg)); }}; }Copy the code

This method, as the name suggests, creates events. A handlers array is defined to hold the callback function events to be handled.

It then returns an object.

The push method is to add functions to the Handlers to execute.

Listen () returns the listen() method, which calls listeners.

Finally, the call() method is easier to understand, taking the callbacks in the Handlers and executing them one by one.

To summarize, store the pushed functions and filter them. The subsequent calls are executed in sequence. Length is the number of functions you currently have.

And if you cut back, you’ll see that every time you call this Listen you’re pushing a function into an internal variable, handlers.

block

Like Listeners, they are created using createEvents, so we don’t need to go into details. So let’s talk about where we’re going to use this.

let blockers = createEvents<Blocker>();
Copy the code
  • block(prompt) – (function) Prevents navigation (see the history docs)

This is the react-router website.

I’m going to give you a quick overview of what happens when a browser is closed or rolled back by mistake. Details can be seen click in view.

conclusion

At this point, the source code for some of the properties returned by our createBrowserHistory call is at hand. But I don’t know exactly how it works.

Specific core principles

Let’s show you the source code. This is the core principle of history. Don’t be fooled by so many lines of code, many of which we explained in the previous push

const PopStateEventType = 'popstate';
let blockedPopTx: Transition | null = null;
function handlePop() {
  / / if you have
  if (blockedPopTx) {
    blockers.call(blockedPopTx);
    blockedPopTx = null;
  } else {
    let nextAction = Action.Pop;
    let [nextIndex, nextLocation] = getIndexAndLocation();

    if (blockers.length) {
      if(nextIndex ! =null) {
        let delta = index - nextIndex;
        if (delta) {
          // Revert the POP
          blockedPopTx = {
            action: nextAction,
            location: nextLocation,
            retry() {
              go(delta * -1); }}; go(delta); }}else {
        // Trying to POP to a location with no index. We did not create
        // this location, so we can't effectively block the navigation.
        warning(
          false.// TODO: Write up a doc that explains our blocking strategy in
          // detail and link to it here so people can understand better what
          // is going on and how to avoid it.
          `You are trying to block a POP navigation to a location that was not ` +
          `created by the history library. The block will fail silently in ` +
          `production, but in general you should do all navigation with the ` +
          `history library (instead of using window.history.pushState directly) ` +
          `to avoid this situation.`); }}else{ applyTx(nextAction); }}}window.addEventListener(PopStateEventType, handlePop);
Copy the code

Window. AddEventListener (PopStateEventType, handlePop)

MDN address: developer.mozilla.org/zh-CN/docs/…

It’s basically listening for changes in the route and then executing a callback function

createHashHistory

This is generally the same as common routing. I want to highlight some issues here.

For example, hash routing anchor problem, I know a few schemes

  • scrollIntoView

  • scrollTop

  • react-anchor-without-hash

Interested in their own inquiry, this is not within the scope of this discussion.

Specific core principles

As mentioned earlier, the core of the history route is window.addeventListener (PopStateEventType, handlePop);

The core of hash routing is also to monitor the change of routing, but the parameters are different.

window.addEventListener('hashchange'.function(e){
    /* Listen for changes */
})
Copy the code

And the change of route. The history routes are history.pushState and history.replaceState.

Hash routes are:

window.location.hash

Get and set the hash value using the window.location.hash property.

Specific words are very similar, concerned about the details of the partner can go to see its source oh.

Core API

We looked at BrowserRouter earlier. Let’s go back and see how much of the example code in the demo remains unsolved:

import React from 'react'; import { BrowserRouter as Router, Switch, Route, Link, } from "react-router-dom"; Function Home() {return (<> <h1> </h1> <Link to="/login"> </Link> </>)} function login () {return (<> <h1> <Link to="/"> </Link> </> } function App() { return ( <Router> <Switch> <Route path="/login" component={Login}/> <Route path="/" component={Home}/> </Switch> </Router> ); } export default App;Copy the code

We have looked at BrowserRouter (Router). There are switches and routes below.

Switch

First on the source code. I took the core part.

class Switch extends React.Component { render() { return ( <RouterContext.Consumer> {context => { invariant(context, "You should not use <Switch> outside a <Router>"); Context. location is used by default, but only if there is a customized location. / / are introduced under the const location = this. Props. The location | | context. The location; let element, match; // We use React.Children.forEach instead of React.Children.toArray().find() // here because toArray adds keys to all child elements and we do not want // to trigger an unmount/remount for two <Route>s that render the same // component at // react.children. ForEach (this.props. Children, If (match == null && react. isValidElement(child)) {element = child; // If you want to use Route, you can also use from to match any component, as long as you have the from attribute under the Switch and it matches the current path. Will be rendered / / the from is specific to the < Redirect > use, later said to const path = child. Props. The path | | child. Props. The from; // Check whether the component matches match = path. matchPath(location.pathname, { ... child.props, path }) : context.match; }}); return match ? React.cloneElement(element, { location, computedMatch: match }) : null; }} </RouterContext.Consumer> ); }}Copy the code

How to use the location function in Switch? What use?

From website

location: object

A location object to be used for matching children elements instead of the current history location (usually the current browser URL).

A location object used to match child elements, rather than the current history location (usually the current browser URL).

matchPath

function matchPath(pathname, options = {}) {
  // Normalize the structure
  // If options is passed as a string, the default string represents path
  // If options is passed as an array, as long as there is a match, it is considered a match
  if (typeof options === "string" || Array.isArray(options)) {
    options = { path: options };
  }

  const { path, exact = false, strict = false, sensitive = false } = options;

  // Convert to array for judgment
  const paths = [].concat(path);

  // All are very simple content, the difficulty lies in the reduce, this is very interesting, interested or do not know, go to MDN to learn about it!!
  return paths.reduce((matched, path) = > {
    if(! path && path ! = ="") return null;
    // If there is a match, return it as a match
    if (matched) return matched;

    // regexp is a regular expression
    // keys is the value of the cut key
    const { regexp, keys } = compilePath(path, {
      end: exact,
      strict,
      sensitive
    });
    The exec() method returns an array of results if a matching text is found, and an array of results otherwise
    const match = regexp.exec(pathname);
    /* Failed to match, return null */
    if(! match)return null;

    // the url represents the matched portion
    const [url, ...values] = match;
    // pathName === URL indicates a full match
    const isExact = pathname === url;

    if(exact && ! isExact)return null;

    return {
      path, // the path used to match
      url: path === "/" && url === "" ? "/" : url, // the matched portion of the URL
      isExact, // whether or not we matched exactly
      params: keys.reduce((memo, key, index) = > {
        memo[key.name] = values[index];
        returnmemo; }, {}}; },null);
}
Copy the code

The matchPath function is also exported by the react-Router function, which can be used to obtain specified parameters in a URL.

Route

A Route is a component layer used to declare Route mapping to an application.

Route has three rendering methods, of course, only one of which will work if all are configured, priority being Children > Component > render

2\1. \ \ 3.

Each is useful in different situations, and in most cases, component is used.

component

Component represents the React component that will render only if the position matches. Create a new React Element using component (instead of Render or children) Route using React. CreateElement (element, props) from the given component. This means that a component created with Component can get props in the router.

children

As shown in the source code, children has a higher priority than Component, and can be a component or function. Children does not get props for the router.

A very special feature of children is that the children method is executed when the route does not match and children is a function, giving the design great flexibility.

render

Render must be a function, with the lowest priority, that is executed when the match is successful.

exact & strict & sensitive

All three are required for path matching using path-to-regexp.

  1. Exact: If true, it matches only if the path exactly matches location.pathname.
  2. Strict: The slash after the pathName of the location is considered when determining whether the location matches the current URL.
  3. Sensitive: True if the path is case sensitive.

location

The Route element tries to match its path to the current browser URL, but you can also use location to match a location other than the current browser location.

The source code for Route is listed below, with the dev section removed.

import React from "react";
import { isValidElementType } from "react-is";
import PropTypes from "prop-types";
import invariant from "tiny-invariant";
import warning from "tiny-warning";

import RouterContext from "./RouterContext.js";
import matchPath from "./matchPath.js";

/** * The public API for matching a single path and rendering. */
class Route extends React.Component {
  render() {
    return( <RouterContext.Consumer> {context => { invariant(context, "You should not use <Route> outside a <Router>"); / / as you can see, the user location to cover off the context of the location of the const location = this. Props. The location | | context. The location; // If there is a computedMatch, use computedMatch as the result. // matchPath calls path-to-regexp to determine the match. // Path-to-regexp requires three parameters. If true, only if the path matches location.pathname exactly // strict: if true, only one slash will match location.pathname // sensitive: True if the path is case sensitive, This.props.com putedMatch // <Switch> already computed the match for us: this.props.path ? matchPath(location.pathname, this.props) : context.match; // Match () const props = {... // props () const props = {... context, location, match }; // let {children, component, render} = this. Props; // Preact uses an empty array as children by // default, so use null if that's the case. If (array.isarray (children) &&children. Length === 0) {children = null; } return (// RouterContext updates location, Match <RouterContext.Provider value={props}> {props. If children is a function, execute, otherwise return children directly? Typeof children === "function" : children(props) : children // Component // There is a component. Create a new component? React. CreateElement (component, props) // Without component, render: render // With render, render method? Function: typeof children === "function" // if (props) = // if (props) = // if (props) = // children(props) : null} </RouterContext.Provider> ); }} </RouterContext.Consumer> ); } } export default Route;Copy the code

The Route component updates some of the attributes (location and Match) in the RouterContext according to its own parameters. If the current path matches the configured path path, the component will be rendered by children. The most common is the component mode. Note the difference between each mode.

Prompt

Prompt is used to Prompt route switching. This can be useful in situations where the user modifs data on a page and then leaves, prompting the user to save or not. The Prompt component has two properties:

  1. Message: Text message used to display prompts.
  2. When: Passes a Boolean value, equivalent to the label switch. Default is true and invalid when set to false.

The essence of a Prompt isto call context.history.block when true to register a route listener for the whole world. Window. confirm is used by default when the path changes. We can also customize the confirm form by passing getUserConfirmation to BrowserRouter or HashRouter. Will replace window.confirm.

import React from "react"; import PropTypes from "prop-types"; import invariant from "tiny-invariant"; import Lifecycle from "./Lifecycle.js"; import RouterContext from "./RouterContext.js"; /** * The public API for prompting the user before navigating away from a screen. */ function Prompt({ message, when = true }) { return ( <RouterContext.Consumer> {context => { invariant(context, "You should not use <Prompt> outside a <Router>"); if (! when || context.staticContext) return null; // call the history.block method const method = context.history.block; return ( <Lifecycle onMount={self => { self.release = method(message); }} onUpdate={(self, prevProps) => { if (prevProps.message ! == message) { self.release(); self.release = method(message); } }} onUnmount={self => { self.release(); }} message={message} /> ); }} </RouterContext.Consumer> ); } export default Prompt;Copy the code

Redirect

Redirect is not so much a component as a set of methods encapsulated by a component that jumps to a new location by calling the History API during the componentDidMount life cycle. By default, the new location overrides the current location in the history stack.

To indicates the url to be redirected to. To can also be a Location object

When push is true, the redirection pushes the new entry into history rather than replacing the current entry.

If a Redirect has a “FROM” attribute, it is retrieved by the Switch. When the “from” attribute matches the current path, the Switch renders the Redirect component and performs the Redirect.

import React from "react";
import PropTypes from "prop-types";
import { createLocation, locationsAreEqual } from "history";
import invariant from "tiny-invariant";

import Lifecycle from "./Lifecycle.js";
import RouterContext from "./RouterContext.js";
import generatePath from "./generatePath.js";

/** * The public API for navigating programmatically with a component. */
function Redirect({ computedMatch, to, push = false }) {
  return (
    // RouterContext has everything<RouterContext.Consumer> {context => { invariant(context, "You should not use <Redirect> outside a <Router>"); const { history, staticContext } = context; // Generally speaking, Redirect operations do not require a history, so history.replace const method = push? history.push : history.replace; Const location = createLocation(// computedMatch) const location = createLocation(// computedMatch typeof to === "string" ? generatePath(to, computedMatch.params) : { ... to, pathname: generatePath(to.pathname, computedMatch.params) } : to ); // When rendering in a static context, // staticRouter if (staticContext) {method(location); return null; } return (<Lifecycle onMount={() => {// componentDidMount when method(location), Replace method(location); }} onUpdate={(self, prevProps) => {// componentDidUpdate Method (location) = componentDidMount Const prevLocation = createLocation(prevProps. To); if ( ! locationsAreEqual(prevLocation, { ... location, key: prevLocation.key }) ) { method(location); }} // void to={to} />); }} </RouterContext.Consumer> ); } export default Redirect;Copy the code

Lifecycle does not render any page, Lifecycle only provides onMount, onUpdate, onUnmount.

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;
Copy the code

conclusion

The entire React-Router is led by createBrowserHistory or createHashHistory, which binds to our React component and passes some methods and values that belong to the history library. And, of course, route matching and rendering.

In the history library there is listening for routes, changing routes and so on.

process

Use the history model as a reference.

Modify the url

When the URL changes, the listener window.addEventListener(‘ popState ‘, handlePop) written on the window is triggered.

Our function handlePop is called

Inside the function we setState, change the location to pass the correct value down, and use the Switch to find the matching Route component.

A component rendering is triggered.

Of course, there are methods called history.push, history.repalce, etc., which essentially change the URL and then repeat the steps above