From the whole to the details, don’t be too rigid in the beginning of the implementation of a line of code, first from the whole to understand, understand their relationship, and then read the source code.

First, let’s not get bogged down in the source details. What does react-Router do? In essence, react-Router matches the path we wrote when the page URL changes, and then renders the corresponding component.

So, from this sentence, let’s think about how to achieve step by step:

  • How do I listen for URL changes?
  • How and by what rules does path match?
  • Render the corresponding component

To understand the key steps needed to be implemented, let’s download the source code of the warehouse. Next, let’s look at GitHub, which uses Lerna management to manage multiple packages simultaneously. The Multirepo concept.

React-router uses LerNA to manage multiple packages at the same time.

The core library is react-router. React-router-dom is used in browsers, and react-router-native is used in RN.

If you do not understand, take a look at the source code to understand. The react-router-dom contains BrowserRouter, Link, NavLink, and HashRouter, all of which reference the React-router directly.

Now that we know how to organize multiple packets, let’s go back to the three key steps of implementing a React-Router:

  • How to listenurlThe change?
  • How to match path
  • Render the corresponding component

We do not realize their own, directly look at the source code, standing on the shoulders of giants to learn 😄. Let’s take a look at the basic usage of the react-router-dom official document.

export default function App() {
  return (
    <BrowserRouter>
      <div>
         <Link to="/">Home</Link>
         <Link to="/about">About</Link>
         <Link to="/topics">Topics</Link>
        <Switch>
          <Route path="/about">
            <About />
          </Route>
          <Route path="/">
            <Home />
          </Route>
        </Switch>
      </div>
    </BrowserRouter>
  );
}

Copy the code

From the code, we can observe the following:

  • The outermost layer is wrapped<BrowserRouter>What does it mean?
  • in <Route />It matches the outer. It’s wrapped<Switch>If one match is made, the other will not be rendered. How to do that?
  • The Route ofpathMatch the path and wrap the rendered component.

The overall design

Let’s use a diagram to understand how the react-Router is implemented:

Let’s look at how each step is implemented.

Listen for URL changes

Normally, the browser sends a request to the server when the URL changes, but it does not send a request to the server using the following two methods:

  • Based on the hash
  • Based on the history

The React-Router uses the history core library.

1. Select History or Hash

The HashRouter first references createBrowserHistory from history, and then passes history and children to the Router. BrowseHistory similarly.

BrowseHistory must rely on the server to map all urls to index.html, otherwise it will be 404.Copy the code

2. Listen for URL changes and get corresponding history, location, match, etc., and inject them into sub-components through the Provider.

2. Match rendering components in Route

This code can be understood in two parts:

  • match
  • Rendering component

1. Check whether the vm is matched

ComputedMatch is a value that uses a subcomponent wrapped in a Switch, which renders from top to bottom. Once one matches, the others do not. So we’re going to judge computedMatch first.

Match path resolution using the third-party path-to-regexp library

// Make sure you consistently `decode` segments.
const fn = match("/user/:id", { decode: decodeURIComponent });

fn("/user/123"); //=> { path: '/user/123', index: 0, params: { id: '123' } }
fn("/invalid"); //=> false
fn("/user/caf%C3%A9"); / / = > {path: '/ user/C3 caf % % A9, index: 0, params: {id:' cafe '}}
Copy the code

2. Component rendering

According to the documentation, it supports rendering in three ways, as follows:

/ / the children
<Route exact path="/">
   <HomePage />
</Route>

/ / func
<Route
   path="/blog/:slug"
   render={({ match}) = > {
     // Do whatever you want with the match...
     return <div />; }} / >

/ / component
<Route path="/user/:username" component={User} />
Copy the code

The source code is as follows:

Jokingly, why can’t the author use “if else” to write so many perverts? : Please do not learn unless your project only has you a front-end 😂.

It doesn’t matter if you don’t understand it all at once. Let’s look at the following flow chart.

From the above code we can see:

  • Router priority: children > Component > render
  • In the case of a mismatch, as long as children is a function, it will also render
  • Component is created using createComponent, which instead of updating an existing component, simply unmounts and mounts a new component. If you pass in component using anonymous functions, the props will be different each time you render the mounted component, resulting in poor performance. Therefore, use render or children when using anonymous functions.

// Don't use it this way
<Route path="/user/:username" component={() = > <User/>} / >Copy the code

conclusion

For packages of the same type with large dependencies, lerNA is used for unified management. Try to abstract out common immutable areas, such as methods in the React-Router.

The React-router uses Compound Components, in which components are used together so that they can easily share an implicit state. Switch, for example, allows the react. children function to control the render priority of the package component without requiring the user to control it. For example, and