preface

I wrote an article about the confusing points in routing. After reading it, I believe you have a good understanding of front-end routing, back-end routing, history and Hash modes and how to distinguish them. In this article, we will introduce the principle of React-Router. React-router is a Router with a Router interface that supports Router functions.

Data interaction between browser apis and components

As analyzed in the previous article, front-end routing leverages some of the browser’s built-in capabilities, mainly window.history combined with popState and Location.hash combined with hashchange. The React Router is a Router that handles the url of the browser and connects to the event callback function. If we want to implement the React Router, we need to interact with the React callback function.

Let’s take a look at how components like react-Router interact with event callbacks. First, the goal is to jump to the page when the URL changes. Each time the URL changes, the browser’s popstate or hashchange callback is reexecuted. The callback needs to tell the React-router that the CURRENT URL has changed, and the React-router needs to update the url correctly once it gets the new URL data. The react-router can update the URL data according to the new URL data as long as the url data is stored in the state. In other words, all we need to do in the browser event callback is update the state in the React-Router.

The most intuitive solution would be to register the popState or hashchange event in the React-Router and update the component’s state in the callback function, but that’s not elegant. And we know that the popState event will not be triggered by the manual pushState, so we need to manually trigger the update state operation after the manual pushState, and the operation of the manual pushState is related to some underlying business components, such as the menu bar. Or a jump button, that is, the lower-level component needs the ability to call the top-level component’s methods, so it’s natural to think of context for value passing. Back to the question of whether event binding should be written in the React-router, because the choice of history mode or hash mode depends entirely on the user. Besides event binding, it also needs to deal with the case of code jump in history mode. Therefore, in order to divide functions clearly, There is already a history NPM package to handle history-related content, and the official React-Router uses this package as well.

What this package does, on the one hand, is bind events based on the type of front-end route the user needs (history/Hash), and use publish-subscribe mode to manage callbacks throughcreateBrowserHistoryorcreateHashHistoryAfter creating a history object, you can call history.listen to register events. Take history mode as an example. The following is a general flowchart

The hash mode is similar. After confirming the general data interaction, we will implement a simple version of the React-Router, also using the history mode as an example.

Common components

First, let’s take a look at what components are needed to apply the React-Router in a typical project and what parameters we need to pass when using these components

  • BroswerRouter
    • This is the component that makes it possible for us to implement the route jump. In practice, using the History mode, we would choose the BroswerRouter component, whose core is the Router component
    • Given the name, the hash mode corresponds to a HashRouter, and what BroswerRouter and HashRouter do is pass different types of history to the Router component
  • Router
    • The core of routing includes data interaction with history and transparent transmission of related data to the lower level.
    • Url changes need to be automatically updated and notified to the sub-components
  • Route
    • Need to configure the path, component/children/render parameters
    • If the path and the current url data conform to render the corresponding component/children/render
  • Switch
    • It can be found from the definition of Route that url data and Path are not necessarily one-to-one. It is possible that a URL corresponds to multiple routes, so multiple Route components are corresponding components of rendering
    • The function of the Switch is to ensure that only one of its sub-components matches, and the others are discarded
  • Redirect
    • The to parameter needs to be configured
    • If the path of any Route does not match the URL, the Route is forwarded to the address indicated by to
  • Link
    • When used in the navigation bar or jump button, the push method on the history object is called internally to update the URL and execute the corresponding callback function, and finally to jump the page

BroswerRouter

This component is a history shell on the Router, so its implementation is relatively simple, we directly write:

import { createBrowserHistory } from 'history'

class BroswerRouter extends React.Component{
  history=createBrowserHistory()
  render(){
    return <Router history={this.history} />}}Copy the code

Router

This component is our core, and as you can see from the implementation of BroswerRouter, history is passed to the Router component as props. The state of the Router component needs to be changed to trigger the Rerender of react. Therefore, we need to create a state that stores the current URL location. The initial value is the value passed in for props. History (it’s ok to just call the URL on the window, but because you already use the history library, all urls, history-related data, and apis are thrown in history). In addition, in order to make state changes, sub-components can be aware of them, and in order to avoid repeated calls in the future, Context is used for data pass-through. A simplified version of the code is as follows:

class Router extends React.Component{
  constructor(props){
    super(props)
    this.state={
      location:props.history.location
    }
  }
  
  componentDidMount() {
    const {history}=this.props
    history.listen(({location}) = >{
      this.setState({
        location
      })
    })
  }
  
  render(){
    const {location}=this.state
    const {children,history}=this.props
    return <RouterContext.Provider value={
      {
        location.history}}children={children} />}}Copy the code

Route

Route valence is a component at the bottom of the routing module. Its function is to compare path and URL, and render corresponding components if they match. Component can use the way such as component/children/render said, because this article is mainly introduce the React – related data flow in the Router, so unified represented component with a component. Children > Component >render = children>component>render Component rendering code in the source code is as follows:

children
? typeof children === "function"
  ? children(props)
  : children
: component
  ? React.createElement(component, props)
  : render
    ? render(props)
    : null
Copy the code

Switch

In my personal understanding, in most cases, we hope that a URL corresponds to a Route component. If there are multiple cases, there will certainly be problems such as poor follow-up maintenance and easy omission of troubleshooting problems. In order to achieve a one-to-one relationship, if you want to implement it inside the Route component, it must obtain the information of the peer Route, which is obviously not reasonable. What about integrating directly into the Router? If implemented on the Router, a one-to-many scenario is not enough in case you really need it, so the React-Router has a Switch component that gives the developer one-to-one or one-to-many options. When used, the relationship between BroswerRouter, Switch, and Route is as follows

<BroswerRouter>
  <Switch>
    <Route />
    <Route />
  </Switch>
</BroswerRouter>
Copy the code

We can see that in the Switch component, we can get all the Route instances through props. Children, so that when the path of one Route is paired with the URL, we can prevent the subsequent comparison and rendering of other routes. From the above analysis, it is not difficult to get the following simple version of the code:

class Switch extends React.Component{
  render(){
    return <RouterContext.Consumer>
      {context=>{
        const {children}=this.props
        let result=null
        result=children.find(child=>{
         return child.props.path===context.location.pathname
        })
        return result ? React.cloneElement(result) : null
      }}
    </RouterContext.Consumer>}}Copy the code

Redirect

It is usually placed at the end of the Route component, indicating that when all the preceding Route components are unpaired, it will Redirect to the position indicated by the Redirect. Combined with the analysis of the Switch above, if the Redirect is instantiated, it means that all the routes above are not matched, so its implementation is roughly as follows:

class LifeCycleHoc extends React.Component{
  
  componentDidMount() {
    this.props.onMount && this.props.onMount() 
  }
}
class Redirect extends React.Component{
  

  render(){
    const {to}=this.props
    return <RouterContext.Consumer>
      {
        context=>{
          return <LifeCycleHoc onMount={()= >{
            console.log(111)
              context.history.push(to)
            }} 
          />
        }
      }
    </RouterContext.Consumer>}}Copy the code

Link

The components mentioned above are at the level of routing architecture, while the Link component is at the level of invocation, which is frequently invoked by developers. Combined with the data interaction between history and Router analyzed above, we only need to get the history object from the Router, and then call the history.push method. Can complete the page jump, its implementation is as follows

class Link extends React.Component{
  render(){
    const {to,children}=this.props
    return (
      <RouterContext.Consumer>
        {context=>{
          return <div onClick={()= >context.history.push(to)}>{children}</div>
        }}
      </RouterContext.Consumer>)}}Copy the code

conclusion

So far, we have implemented several core components of the React-Router. Finally, we use a diagram to sort out the entire architectureFinally, make up what happens when the user clicks on a navigation bar

Complete example code

Codesandbox. IO/s/quirky – ch…

Refer to the article

Juejin. Cn/post / 688629…