The reason

Currently, the project I am responsible for has a wechat webpage and uses react technology stack. After adding a wechat sharing function in this project, there was a problem with ios online. After investigation, the routing system of React was located.

I decided to take a look at the react-router-dom and see what was going on inside it.

The react-router-dom library provides two advanced routers, BrowserRouter and HashRouter. The difference between the two routers is that one uses the history API, and the other uses the HASH part of the URL. Let me do an interpretation using BrowserRouter as an example.

Simplified process diagram


Interpretation (just extract the core code for demonstration)

What does the react-router-dom provide?

export {
  MemoryRouter,
  Prompt,
  Redirect,
  Route,
 Router,  StaticRouter,  Switch,  generatePath,  matchPath,  withRouter,  useHistory,  useLocation,  useParams,  useRouteMatch } from "react-router";  export { default as BrowserRouter } from "./BrowserRouter.js"; export { default as HashRouter } from "./HashRouter.js"; export { default as Link } from "./Link.js"; export { default as NavLink } from "./NavLink.js"; Copy the code

The react-Router is a react-router, and the react-Router is a react-router, and the react-Router is a react-router.

1. Start with a simple demo

import { BrowserRouter, Route, Switch, Link } from "react-router-dom"

function App() {
   return (
      <BrowserRouter>
<div> Main menu </div> <Link to="/home">home</Link>  <br />  <Link to="/search">search</Link>  <hr />  <Switch>  <Route path="/home" component={Home} />  <Route path="/search" component={Search} />  </Switch>  </BrowserRouter>  ) }  ReactDOM.render(<App />, document.getElementById('root')); Copy the code

Components that need a Route jump to implement UI changes are wrapped around BrowserRouter as a root component, and Route is used to hold page-level components.

So with that hierarchy in mind, let’s first look at what BrowersRouter implements.

2. BrowersRouter

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

With a few lines of code, it’s clear that the core is the function provided by the history library. The component executes createHistory before Render, and it returns an instance of history, which is then passed to the Router via props, and all of its wrapped child components, which are then passed to the Router.

Here in fact the official website has said very clearly, we can pay more attention to when using.


How does the Router use the history object? What is the difference between the history object and window.history? Let’s keep going.

3. Router

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

The Router is the core Router, and we saw above that BrowsRouter passes it a History object.

So we’re going to introduce two contexts, and here we’re just going to create a normal context with a specific name.

Its internal implementation looks like this

const createNamedContext = name => {
  const context = createContext();
  context.displayName = name;
  return context;
};
 HistoryContext = createNamedContext("Router-History") Copy the code

Now that we’ve introduced these two contexts, let’s look at its constructor.

constructor(props) {
    super(props);
    this.state = {
      location: props.history.location
    };
  this.unlisten = props.history.listen(location => {   this.setState({ location });   });   } Copy the code

The Router component maintains an internal state Location object with the initial value provided by the history created in BrowsRouter mentioned above.

After that, the listen function provided by the history object is executed. This function requires a callback function as an input parameter. The callback function is used to update the location of the current Router’s internal state.

 componentWillUnmount() {
    if (this.unlisten) {
       this.unlisten();
     }
  }
Copy the code

When the Router component is about to uninstall, unlisten for history.

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

The react tree is made up of the original context, and then passes in values like history and location.


In summary, the Router is a context provider that passes in history, locaiton, and other data, which can then be shared by its child components as consumers for route jumps, UI updates, and other actions.

4. The histroy

In the Router component you can see that the history instance returned by createBrowserHistory has been used, as in: History. location and history.listen, there are quite a few functions encapsulated in this library, and there are a lot of details, so I’ll still pick the most important ones.

The first is what attributes and methods are provided by our highly publicized history


These look familiar: push, replace, and go are all provided by the Window object property history. But some attributes are actually overwritten, such as push and replace, while others are simply encapsulated.

  function goBack() {
    go(-1);
  }
  function goForward() {
    go(1);
 } Copy the code

The initial data for the Router’s internal state location is a recombination of window.location and window.history. State.

The two most important page switching actions in the routing system are push and replace. We don’t have a definite feeling when we only use the Link component, in which Link accepts a props attribute, to: String or to: Object

<link to='/course'>

When you click on it, you call the push method overridden in props. History.

<Link to='/course' replace> jump </Link>

If the replace attribute is added, the replace method is used

These methods mainly use the pushState and replaceState apis, which provide the ability to add a new history in window.history and a URL to the browser’s address bar without actually making a web request.

This is the key to a single page application.

Then let’s look at the two route jump methods


So we have a lot of code. Let me read that.

Path, the entry parameter in push, is the next route address to be prepared to jump to. The createLocation method first merges this path with the current location and returns an updated LOation.

Next comes the transitionManager object, and let’s focus on the contents of the successful callback.

Use the updated location to create the href to jump to, and call pushState to update the history in window.history.

If you pass the forceRefresh attribute to BrowserRouter, you will then modify window.lcoation. Href directly to jump to the page, but this will be equivalent to refreshing the web to request your file resources.

If not, the setState function is called. Note that this setState is not provided by React, but is implemented by the history library itself.

function setState(nextState) {
    history.length = globalHistory.length;
    transitionManager.notifyListeners(history.location, history.action);
  }
Copy the code

Again, a method of the transitionManager object is used.

In addition, the window.history obtained after pushState is updated.

This leaves the transitionManager, the last dot.

TransitionManager is an object instantiated with createTransitionManager

  function createTransitionManager() {
   var listeners = [];
  function appendListener(fn) {
    var isActive = true;
    function listener() {
 if (isActive) fn.apply(void 0, arguments);  }  listeners.push(listener);  return function () {  isActive = false;  listeners = listeners.filter(function (item) {  returnitem ! == listener; });  };  }   function notifyListeners() {  for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {  args[_key] = arguments[_key];  }  listeners.forEach(function (listener) {  return listener.apply(void 0, args);  });  }   return { appendListener: appendListener, notifyListeners: notifyListeners };  } Copy the code

Remember we have been used in the Router components at the beginning of a history. Listen method, which the internal implementation is to use transitionManager. AppendListener method

function listen(listener) {
    var unlisten = transitionManager.appendListener(listener);
    checkDOMListeners(1);
    return function () {
      checkDOMListeners(-1);
 unlisten();  };  } Copy the code

Listen is passed a callback function that updates the locaton data of the component’s internal state via React setState, and since the LCOation is passed into the router-context value when it changes, All consumer components are rerendered to update the UI.

The implementation details of Listen are that its input function (in this case the function that updates the state.location of the Rrouter) is passed into appendListener.

AppendListener pushes the function into the Listeners array and saves it. It then returns a function that removes the function that pushed the array, thus canceling the listening function.

So when we switch routes using push, it executes a notifyListeners and passes in the updated location.

The listeners are then iterated through the callbacks we passed in Listen, and the listeners are the final listeners to update the Router’s location.

The Route component of the Router decides whether to render the page component by matching the pathName with the updated location, and the whole Route jump process ends.

conclusion

Read the source code for the first time, although cut a lot, but still write a lot.

I hope you can go along with this idea and have a look yourself. There are still a lot of details worth considering.

Finally, feel helpful, hope to give a thumbs-up.