React Router 4 has core differences from previous versions

React Router 4 differs from previous versions mainly in the location of the Router in the project. V2 / V3 treats the Router as a unit, separated from other components, and centrally managed in a separate Router file. Also, the nesting of layout and pages is determined by the nesting of routes.

import { Router, Route, IndexRoute, browserHistory } from 'react-router' const PrimaryLayout = props => ( <div className="primary-layout"> <header> Our React Router 3 App </header> <main> {props.children} </main> </div> ) const HomePage =() => <div>Home Page</div> const UsersPage = () => <div>Users Page</div> Const App = () => (<Router history={browserHistory}> <Route path="/" Component ={PrimaryLayout}> <IndexRoute component={HomePage} /> <Route path="/users" component={UsersPage} /> </Route> </Router> ) render(<App />, document.getElementById('root'))Copy the code

In version V4, the router is split into its own modules. There is no separate Router module, which fully embodies the idea of componentization. Also, the use of is different from the way it was passed in as the history property. // The reason v4 changed from ‘react-router-dom’ is because there is also a native version, which means the Web version

import { BrowserRouter, Route } from 'react-router-dom' const PrimaryLayout = () => ( <div className="primary-layout"> <header> Our React Router  4 App </header> <main> <Route path="/" exact component={HomePage} /> <Route path="/users" component={UsersPage} /> </main> </div> ) const HomePage =() => <div>Home Page</div> const UsersPage = () => <div>Users Page</div> const App = ()  => ( <BrowserRouter> <PrimaryLayout /> </BrowserRouter> ) render(<App />, document.getElementById('root'))Copy the code

React Router V4 makes it easier to understand the relationship between a Route and a component. You can use a Route just like a component, except that the URL is its path. When path matches, the component content corresponding to Route is rendered.

Inclusive routes and Exact

In previous versions, the path written in Route was unique for routing matches, while version V4 has an inclusion relationship: routes that match path=”/users” will match paths that match path=”/”, and both modules will render simultaneously on the page. Therefore, v4 has the keyword exact, which means that only the current route is matched.

// When matched with /users, UsersMenu and UsersPage const PrimaryLayout = () => (<div className="primary-layout"> <header> Our React Router 4 App <Route path="/users" component={UsersMenu} /> </header> <main> <Route path="/" exact component={HomePage} /> <Route path="/users" component={UsersPage} /> </main> </div> )Copy the code

Independent route: Switch

If you want to match only one route, you can use the Swtich component in addition to the exact attribute.

const PrimaryLayout = () => ( <div className="primary-layout"> <PrimaryHeader /> <main> <Switch> <Route path="/" exact Component ={HomePage} /> // exact must be added. <Route path="/users/add" component={UserAddPage} /> <Route path="/users" component={UsersPage} /> <Redirect to="/" /> </Switch> </main> </div> )Copy the code

Instead, only one route is rendered, and the first matching component is always rendered. Therefore, in the first route, we still need to use exact, otherwise when we render ‘/users’ or ‘/users/add’, only the components that match ‘/’ will be displayed (PS: if not, when we do not use exact, we will render multiple components that match). Therefore, it is better to place the ‘/user/add’ route before the ‘/users’ route because the latter contains the former. Of course, we can use exact as well, so that we don’t care about the order. As for the component, when used alone, the browser redirects once a route is matched; In combination, redirection is performed only when no route matches. For example, in the example above, typing ‘/test’ in the address bar leads to ‘/’ and renders the HomePage page.

Fourth, nested layout

First, the author presents a nested layout scenario: if you want to extend the “user module”, you need a “Browse user” page and a “per-user profile” page, as well as the “product module”. For this kind of scenario, the author gives two implementation schemes for comparison. First, the easiest one to think of, but not so ideal:

const PrimaryLayout = props => {
  return (
    <div className="primary-layout">
      <PrimaryHeader />
      <main>
        <Switch>
          <Route path="/" exact component={HomePage} />
          <Route path="/users" exact component={BrowseUsersPage} />
          <Route path="/users/:userId" component={UserProfilePage} />
          <Route path="/products" exact component={BrowseProductsPage} />
          <Route path="/products/:productId" component={ProductProfilePage} />
          <Redirect to="/" />
        </Switch>
      </main>
    </div>
  )
}

const BrowseUsersPage = () => (
  <div className="user-sub-layout">
    <aside>
      <UserNav />
    </aside>
    <div className="primary-content">
      <BrowseUserTable />
    </div>
  </div>
)

const UserProfilePage = props => (
  <div className="user-sub-layout">
    <aside>
      <UserNav />
    </aside>
    <div className="primary-content">
      <UserProfile userId={props.match.params.userId} />
    </div>
  </div>
)
Copy the code

You can easily see that the layouts of BrowseUsersPage and UserProfilePage are duplicated, rendering the overall layout each time a child page is rendered. If the project is large, there will be a lot of redundant code, which will affect overall performance. Let’s look at a better approach that takes advantage of the componentization idea of Route:

const PrimaryLayout = props => { return ( <div className="primary-layout"> <PrimaryHeader /> <main> <Switch> <Route path="/" exact component={HomePage} /> <Route path="/users" component={UserSubLayout} /> <Route path="/products" component={ProductSubLayout} /> <Redirect to="/" /> </Switch> </main> </div> ) } const UserSubLayout = () => ( <div className="user-sub-layout"> <aside> <UserNav /> </aside> <div className="primary-content"> <Switch> <Route path="/users" exact component={BrowseUsersPage} /> <Route path="/users/:userId" component={UserProfilePage} /> </Switch>  </div> </div> ) const BrowseUsersPage = () => <BrowseUserTable /> const UserProfilePage = props => <UserProfile userId={props.match.params.userId} />Copy the code

In this method, the module containing sub-pages is treated as a single whole module, and then the sub-modules are nested within the module, so that when the whole module is rendered, the layout is rendered once; When a submodule route is matched, only that part of the submodule is rendered separately. One thing to note is that the child page still needs to specify the path of the parent module (e.g. ‘/users’) in order to be matched.

const UserSubLayout = ({match}) => ( <div className="user-sub-layout"> <aside> <UserNav /> </aside> <div className="primary-content"> <Switch> <Route path={match.path} exact component={BrowseUserTable} /> <Route path={`${match.path}/:userId`} component={UserProfilePage} /> </Switch> </div> </div> ) const UserProfilePage = Const UserNav = () => (<div>User Nav</div>) const UserNav = () => (<div>User Nav</div>) const BrowseUserTable = ({match}) => ( <ul> <li><Link to={`${match.path}/bob`}>Bob</Link></li> <li><Link to={`${match.path}/Tom`}>Tom</Link></li> <li><Link to={`${match.path}/Jack`}>Jack</Link></li> </ul> ) const UserProfile = ({ userId }) => <div>User: {userId}</div>;Copy the code

You can also optimize the route by using match and other methods to reduce repetitive code input:

Five, the Match

Match contains four properties: mate. params, mate. isExact, mate. path, and mate. url. Take a look at the match attribute:

1) match-. path vs. match-. url When there are no arguments, match-. path and match-. url are the same, but when there are arguments, there is a difference:

  • Path: specifies the path argument written in.
  • Match-. url: the actual URL displayed in the browser.
const UserSubLayout = ({ match }) => {
    console.log(match.path)   // output: "/users"
    console.log(match.url)  // output: "/users"
    return (
      <div className="user-sub-layout">
        <aside>
          <UserNav />
        </aside>
        <div className="primary-content">
          <Switch>
            <Route path={match.path} exact component={BrowseUserTable} />
            <Route path={`${match.path}/:userId`} component={UserProfilePage} />
          </Switch>
        </div>
      </div>
    )
  }

const UserProfilePage = ({match}) => {
    console.log(match.path); // output: "/users/:userId"
    console.log(match.url); // output: "/users/bob"
    return <UserProfile userId={match.params.userId} />
}
Copy the code

The authors strongly recommend using mate. path when writing routing paths, because using mate. url can end up producing unexpected scenarios, such as the following example:

const UserComments = ({ match }) => {
    console.log(match.params);  // output: {}
    return <div>UserId: {match.params.userId}</div>
}

const UserSettings = ({ match }) => {
    console.log(match.params);  // output: {userId: "5"}
    return <div>UserId: {match.params.userId}</div>
}

const UserProfilePage = ({ match }) => (
  <div>
    User Profile:
    <Route path={`${match.url}/comments`} component={UserComments} />
    <Route path={`${match.path}/settings`} component={UserSettings} />
  </div>
)
Copy the code

Render ‘UserId: undefined’ when accessing ‘/users/5/comments’; Render ‘UserId: 5’ when accessing ‘/users/5/ Settings’. Why does matter. path render properly, but not with matter. url? The reason for this difference is that {${mate. url}/comments} hardcodes {‘/users/5/comments’}. There are no arguments in the path, only a dead 5. It doesn’t render properly. Mate. path can be used to construct nested

, while mate. url can be used to construct nested . 2) How to avoid conflict of Match? Consider this scenario: From the previous examples, if you wanted to add and edit users via ‘/users/add’ and ‘/users/5/ Edit ‘, but we knew that users/:userId already pointed to UserProfilePage, from the previous examples, Does that mean users/:userId needs to point to a sub-page module that can edit and preview at the same time? Not necessarily, since Edit and profile share a subpage, this can be done in the following way:

const UserSubLayout = ({ match }) => (
  <div className="user-sub-layout">
    <aside>
      <UserNav />
    </aside>
    <div className="primary-content">
      <Switch>
        <Route exact path={props.match.path} component={BrowseUsersPage} />
        <Route path={`${match.path}/add`} component={AddUserPage} />
        <Route path={`${match.path}/:userId/edit`} component={EditUserPage} />
        <Route path={`${match.path}/:userId`} component={UserProfilePage} />
      </Switch>
    </div>
  </div>
)
Copy the code

Placing the Add and Edit pages before the profile allows on-demand matching. If the profile path is first, the Profile page will be matched when the Add page is accessed because the Add matches :userId. There is an alternative to putting profiles first: Use the path-to-regexp to constrain the path, for example, ${mate. path}/:userId(\d+). In this way, the userId can only be number. In this way, there is no conflict when accessing the /users/add path.