Interpretation of new features of React-Router v6 and migration guide

created at 11-12-2021 views: 32

Preface

In early 18th, the main developers of React Router created a lightweight alternative called Reach Router.

It turned out to be competing with each other, but I didn’t expect React Router to merge it directly (good!)

At present, v6 is the last version of the test. It is estimated that the new features are as follows.

  1. <Switch> is renamed to <Routes>.
  2. New features of <Route> are changed.
  3. Nested routing becomes simpler.
  4. Use useNavigate instead of useHistory.
  5. The new hook useRoutes replaces react-router-config.
  6. Size reduction: from 20kb to 8kb

1. <Switch> renamed to <Routes>

The top-level component will be renamed. However, most of its functions remain unchanged

// v5
<Switch>
    <Route exact path="/"><Home /></Route>
    <Route path="/profile"><Profile /></Route>
</Switch>

// v6
<Routes>
    <Route path="/" element={<Home />} />
    <Route path="profile/*" element={<Profile />} />
</Routes>

2. New feature changes of <Route>

component/render is replaced by element

All in all, in a nutshell. It just becomes better to use.

import Profile from './Profile';

// v5
<Route path=":userId" component={Profile} />
<Route
  path=":userId"
  render={routeProps => (
    <Profile routeProps={routeProps} animate={true} />
  )}
/>

// v6
<Route path=":userId" element={<Profile />} />
<Route path=":userId" element={<Profile animate={true} />} />

3. Nested routing becomes simpler

The specific changes are as follows:

  • <Route children> has been changed to accept child routes.
  • Simpler matching rules than <Route exact> and <Route strict>.
  • <Route path> The path hierarchy is clearer.

3.1 Simplify nested route definition

Nested routing in v5 must be very clearly defined, and requires a lot of string matching logic in these components (I finally realized this problem.)

Previous treatment:

// v5
import {
  BrowserRouter,
  Switch,
  Route,
  Link,
  useRouteMatch
} from 'react-router-dom';

function App() {
  return (
    <BrowserRouter>
      <Switch>
        <Route exact path="/" component={Home} />
        <Route path="/profile" component={Profile} />
      </Switch>
    </BrowserRouter>
  );
}

function Profile() {
  let { path, url } = useRouteMatch();

  return (
    <div>
      <nav>
        <Link to={`${url}/me`}>My Profile</Link>
      </nav>

      <Switch>
        <Route path={`${path}/me`}>
          <MyProfile />
        </Route>
        <Route path={`${path}/:id`}>
          <OthersProfile />
        </Route>
      </Switch>
    </div>
  );
}

In v6, you can delete the string matching logic. No need for any useRouteMatch()

// v6
import {
  BrowserRouter,
  Routes,
  Route,
  Link,
  Outlet
} from 'react-router-dom';

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="profile/*" element={<Profile/>} />
      </Routes>
    </BrowserRouter>
  );
}

function Profile() {
  return (
    <div>
      <nav>
        <Link to="me">My Profile</Link>
      </nav>

      <Routes>
        <Route path="me" element={<MyProfile />} />
        <Route path=":id" element={<OthersProfile />} />
      </Routes>
    </div>
  );
}

Of course, there is a more sour operation, directly define the <Route> of <Route> in the route, and then use the next new API: Outlet

3.2 New API: Outlet

This thing is very much like {this.props.children}. For specific usage, see the following example:

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="profile" element={<Profile />}>
          <Route path=":id" element={<MyProfile />} />
          <Route path="me" element={<OthersProfile />} />
        </Route>
      </Routes>
    </BrowserRouter>
  );
}

function Profile() {
  return (
    <div>
      <nav>
        <Link to="me">My Profile</Link>
      </nav>
        {/*
       将直接根据上面定义的不同路由参数,渲染<MyProfile />或<OthersProfile />
        */}
      <Outlet />
    </div>
  )
}

3.3 Multiple <Routes />

Previously, we could only use one Routes in React App. But now we can use multiple routes in React App, which will help us manage multiple application logic based on different routes.

import React from 'react';
import { Routes, Route } from 'react-router-dom';

function Dashboard() {
  return (
    <div>
      <p>Look, more routes!</p>
      <Routes>
        <Route path="/" element={<DashboardGraphs />} />
        <Route path="invoices" element={<InvoiceList />} />
      </Routes>
    </div>
  );
}

function App() {
  return (
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="dashboard/*" element={<Dashboard />} />
    </Routes>
  );
}

4. Use useNavigate instead of useHistory

Changed from being clear at a glance to blindness. . .

I always feel that the React Router team is a little bit playful. . .

// v5
import { useHistory } from 'react-router-dom';

function MyButton() {
  let history = useHistory();
  function handleClick() {
    history.push('/home');
  };
  return <button onClick={handleClick}>Submit</button>;
};

Now, history.push() will be replaced with navigation():

// v6
import { useNavigate } from 'react-router-dom';

function MyButton() {
  let navigate = useNavigate();
  function handleClick() {
    navigate('/home');
  };
  return <button onClick={handleClick}>Submit</button>;
};

The usage of history will also be replaced with:

// v5
history.push('/home');
history.replace('/home');

// v6
navigate('/home');
navigate('/home', {replace: true});

5. The new hook useRoutes replaces react-router-config.

It feels like another wave of forced hooks, but it is still a bit more concise than before. . .

function App() {
  let element = useRoutes([
    { path: '/', element: <Home /> },
    { path: 'dashboard', element: <Dashboard /> },
    { path: 'invoices',
      element: <Invoices />,
      children: [
        { path: ':id', element: <Invoice /> },
        { path: 'sent', element: <SentInvoices /> }
      ]
    },
    // redirect
    { path: 'home', redirectTo: '/' },
    // 404
    { path: '*', element: <NotFound /> }
  ]);
  return element;
}

6. Size reduction: from 20kb to 8kb

While React Router v6 brings us convenience, it also reduces the size of the package by more than half. . .

Size reduction: from 20kb to 8kb

7. Migration and other important repairs...

In fact, the new features listed above are basically all the content of the migration.

The basic starting method is to update the package:

$ npm install react-router@6 react-router-dom@6
# or, for a React Native app
$ npm install react-router@6 react-router-native@6

Among them, I think I need to pay special attention to: React Router v6 uses a simplified path grid and only supports 2 kinds of placeholders: dynamic: id style parameters and * wildcards

The following are all valid routing paths in v6:

/groups
/groups/admin
/users/:id
/users/:id/messages
/files/*
/files/:id/*
/files-*

The path that uses RegExp regular matching will be invalid:

/users/:id?
/tweets/:id(\d+)
/files/*/cat.jpg

All path matching in v6 will ignore the trailing "/" on the URL. In fact, <Route strict> has been removed and is invalid in v6. This does not mean that you do not need to use slashes.

The path before v5 has routing ambiguity

  1. Current path: "/users", then <Link to="me"> will jump to <a href="/me">.
  2. Current path: "/users/", then <Link to="me"> will jump to <a href="/users/me">.

React Router v6 fixes this ambiguity and cancels the trailing "/":

  1. Current path: "/users", then <Link to="me"> will jump to <a href="/users/me">.
  2. Current path: "/users", then <Link to="../me"> will jump to <a href="/me">.

Its form is more like the usage of command line cd:

// The current path is /app/dashboard
<Link to="stats"> // <a href="/app/dashboard/stats">
<Link to="../stats"> // <a href="/app/stats">
<Link to="../../stats"> // <a href="/stats">
<Link to="../../../stats"> // <a href="/stats">

// The current path of the command line is /app/dashboard
cd stats // pwd is /app/dashboard/stats
cd ../stats // pwd is /app/stats
cd ../../stats // pwd is /stats
cd ../../../stats // pwd is /stats
created at:11-12-2021
edited at: 11-12-2021: