The React Router is essential when developing single-page applications using React. When I first got to know the React Router, I followed the documents step by step. Although I didn’t understand some concepts, I still finished the project. Then I read the React Router 4 article you didn’t know about and realized that some of the previous uses in the project were not quite correct. In the process of learning, I took some detours. This article is sort out and summarize my own experience, hoping to bring some inspiration to readers. Click here for the full address of the project, which uses ant.design.

1. Dynamic Routing

React Router V4 introduces the concept of Dynamic Routing. Dynamic Routing corresponds to Static Routing. If you have used Express or KOA, you are familiar with static routing. In the following example, we use koa-route instead of binding to the corresponding Controller.

const route = require('koa-route')
 
app.use(route.get('/article', Controller1))
app.use(route.get('/article/:id', Controller2))
app.use(route.get('/article/:id/edit', Controller3))
Copy the code

The above form is a static route. The most obvious feature of static routing is that all routes to be processed are listed in the code. When the project starts running, we know how all the routes correspond to the Controller. Simply put, the routing representation is constant.

React Router treats everything as a component, and Route is a component. Let’s say I have a Route that looks like this.

<Route path="/article" component={ArticleList} />
Copy the code

If the Route is not already rendered, the ArticleList component will not render at all when we open the /article link. Routes are valid only if the Route is rendered. The routing table is now changed compared to the previous static routes. Routes can be added dynamically at run time. When the page is opened, it is not always possible to know the mapping between all routes and components.

Because the routing is constantly changing, the components we write are very different from what they used to be. Consider the following single web application.

We observed that the entire site could be divided into two types of pages: login pages and other pages. In app.js, you can add two routes first.

// App.js
<Switch>
  <Route path="/login" exact component={Login} />
  <Route path="/" component={PrimaryLayout} />
</Switch>
Copy the code

Routes can be added continuously, so we don’t need to list all of them in app.js right now. In primarylayout.js, add the required route.

// PrimaryLayout.js
<Switch>
  <Route path="/article" exact component={ArticleList} />
  <Route path="/article/:id" exact component={ArticleDetail} />
  <Route path="/article/:id/edit" exact component={ArticleEdit} />
</Switch>
Copy the code

2. The Route component

The Route component has a simple function: it renders the component when the link matches the rules. Notice in the code above that the Route component is nested within the Switch component. When a link matches multiple routes, multiple components are rendered. If Route is nested within the Switch, only the first Route that matches the rule is rendered.

Route has a prop called Render. Setting up the render function allows you to do complex logic in the route.

<Switch>
  <Route path="/login" exact
    render={() => auth ? <Redirect to="/product" /> : <Login  />
  <Route path="/" 
    render={() => auth ? <PrimaryLayout/> : <Redirect to="/login"/>} />
</Switch>
Copy the code

The variable auth is the login status of the user. If the user has logged in, he/she cannot access the login page directly, and if the user has not logged in, he/she cannot access the page that requires permission later. For more complex permission management, write the render function the same way.

3. Router component and History

The Router component is the lower-level component. In real development, we usually choose BrowserRouter or HashRouter.

// index.js
import { BrowserRouter } from 'react-router-dom'
ReactDOM.render(
  <Provider store={store}>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </Provider>.document.getElementById('root'))
Copy the code

BrowserRouter and HashRouter both encapsulate the Router and come with a history object. The biggest difference between the two is their own history object.

import { createBrowserHistory, CreateHashHistory} from 'history' const history = createBrowserHistory( createHashHistory() <Router history={history}> <App/> </Router>Copy the code

The props for BrowserRouter and HashRouter, such as Basename, getUserConfirmation, etc., can be set when creating the history object.

const history = createBrowserHistory({
  basename: '/admin'
})
Copy the code

4. withRouter

WithRouter is a high-level component that injects match, location, and History objects into the props of the component. This is a very useful function. Here are four small examples to illustrate its use.

  1. Work with Redux’s Connect

Earlier we said that routes are components and routing tables are constantly changing. Redux was used in the project to manage the data so that components would not be re-rendered when the data did not change. Suppose a Route component in the component is not rendered and the data does not change, and even if the link on the current page changes, no Route matches that link. Because the Route component is still not rendered! How do you know if the link has changed? That’s where the withRouter comes in.

import { withRouter } from 'react-router-dom'
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Component))
Copy the code
  1. Gets the current route

As shown in the figure below, the sidebar on the left should decide which sections to expand and which to highlight depending on the link changes. WithRouter encapsulates the left component so that it can respond to link changes.

class LeftSider extends React.Component {
  componentDidMount() {
    this.setHighLightKeys(this.props)
  }

  componentWillReceiveProps(nextProps) {
    this.setHighLightKeys(nextProps)
  }

  shouldComponentUpdate(nextProps, nextState) {
    const { match, location } = this.props
    if(! (match === nextProps.match && location === nextProps.location)) {return true
    }
    returnnextState ! = =this.state
  }
}

export default withRouter(LeftSider)
Copy the code

Note that shouldComponentUpdate only compares the match and location values, not the history object. The history object changes each time, so it is not used for comparison.

The same goes for bread crumbs.

  1. Page jump

The React Router provides Link, NavLink, and Redirect components to Redirect pages. However, I control the page jump in a Button click event, and these components are not available. In this case, you might want to use the Location object.

// Wrong way!!
location.href = '/article'
Copy the code

It works, but it’s not right. If the BrowserRouter used previously becomes a HashRouter, this approach will not work. The components encapsulated by withRouter contain the history object to control the jump to the page. The history object has methods like push, replace, and go, which are called to jump to the page.

class Comoponent extends React.Component {
  handleClick () {
    this.props.history.push('/article')}}export default withRouter(Component)
Copy the code
  1. Gets the parameters in the route

In the ArticleDetail component above, we need to know what id is in the current route. The match object for the component props contains the parameters of the route.

class ArticleDetail extends React.Component {
  state = {
    id: null
  }
  componentDidMount () {
    const { id } = this.props.match
    this.setState({ id })
  }
}
Copy the code

5. Code separation

It’s much easier now to load components asynchronously using React-loadable. The previous React Router documentation implemented asynchronous loading of components as follows.

// A more verbose approach
import Component from 'bundle-loader! ./Component'
// Write a component to do this
class Bundle extends React.Component {
  state = {
    // short for "module" but that's a keyword in js, so "mod"
    mod: null
  }

  componentWillMount () {
    this.load(this.props)
  }

  componentWillReceiveProps (nextProps) {
    if(nextProps.load ! = =this.props.load) {
      this.load(nextProps)
    }
  }

  load (props) {
    this.setState({
      mod: null
    })
    props.load((mod) = > {
      this.setState({
        // handle both es imports and cjs
        mod: mod.default ? mod.default : mod
      })
    })
  }

  render () {
    return this.state.mod ? this.props.children(this.state.mod) : null}}// Load asynchronous components
<Bundle load={Component}>
    {(Container) => <Container {. props} / >}
</Bundle>
Copy the code

If you use React-loadable, this is done in just a few lines of code.

import Loadable from 'react-loadable'

const Loading = (a)= > <Spin />

const LogIn = Loadable({
  loader: (a)= > import('.. /components/Login'),
  loading: Loading
})
Copy the code

Further, name these split files by chunk or group asynchronous components into chunks.

const LogIn = Loadable({
  loader: (a)= > import(/* webpackChunkName: "Login" */'.. /components/Login'),
  loading: Loading
})
Copy the code

6. Summary

This article summarizes the important knowledge points of the React Router, and discusses the issues to pay attention to when using the React Router based on my own development experience. Since much of the code in this article is just snippets, intended only to illustrate concepts, see the project’s source code for details.

The resources
  • The React the Router documentation
  • The history of the React the Router
  • Introduction to asynchronous components in the create-react-app documentation