1 the introduction

React Router V6 Alpha has been released, and this week’s Sneak Peek at React Router V6 examines the changes.

2 an overview

renamed

A minor change to make API naming more formal.

// v5
import { BrowserRouter, Switch, Route } from "react-router-dom";

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

React Router v6 uses Routes instead of Switch:

// v6
import { BrowserRouter, Routes, Route } from "react-router-dom";

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

upgrade

In version v5, it is not intuitive to pass parameters to components. You need to pass routeProps as RenderProps:

import Profile from './Profile';

// v5
<Route path=":userId"component={Profile} /> <Route path=":userId" render={routeProps => ( <Profile {... routeProps} animate={true} /> )} /> // v6 <Route path=":userId" element={<Profile />} /> <Route path=":userId" element={<Profile animate={true} />} />Copy the code

In v6, the Render and Component schemes are merged into the Element scheme, which can easily pass props without passing through the roteProps parameter.

More convenient set routines by

In version v5, nested routines need to get a match through useRouteMatch, and implement subroutes through match-. path stitching:

// 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 match = useRouteMatch();

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

      <Switch>
        <Route path={`${match.path}/me`}>
          <MyProfile />
        </Route>
        <Route path={`${match.path}/:id`}>
          <OthersProfile />
        </Route>
      </Switch>
    </div>
  );
}
Copy the code

In v6, useRouteMatch is omitted and the relative path is supported:

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

// Approach #1
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>
  );
}

// Approach #2
// You can also define all
// <Route> in a single place
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>

      <Outlet />
    </div>
  );
}
Copy the code

Notice that an Outlet is an Element of the render child path.

Alternative useHistory useNavigate

In version v5, active forward routes can use useHistory for history.push and other operations:

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

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

In v6, this common operation can be implemented directly via useNavigate:

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

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

The react-router encapsulates history internally. If you need history.replace, you can specify it with {replace: true} :

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

// v6
navigate("/home");
navigate("/home", { replace: true });
Copy the code

Smaller size 8KB

Because the code is almost refactored, the compressed size of the V6 version is reduced from 20KB to 8KB.

3 intensive reading

React-router V6 source code has a paragraph of the core concept, I took out to share with you, some framework development is of great benefit. Let’s look at the code excerpt from useRoutes:

export function useRoutes(routes, basename = "", caseSensitive = false) {
  let {
    params: parentParams,
    pathname: parentPathname,
    route: parentRoute
  } = React.useContext(RouteContext);

  if (warnAboutMissingTrailingSplatAt) {
    // ...
  }

  basename = basename ? joinPaths([parentPathname, basename]) : parentPathname;

  let navigate = useNavigate();
  let location = useLocation();
  let matches = React.useMemo(
    (a)= > matchRoutes(routes, location, basename, caseSensitive),
    [routes, location, basename, caseSensitive]
  );

  // ...

  // Otherwise render an element.
  let element = matches.reduceRight((outlet, { params, pathname, route }) = > {
    return (
      <RouteContext.Provider
        children={route.element}
        value={{
          outlet.params: readOnly({ . parentParams.. params }),
          pathname: joinPaths([basename.pathname]),
          route}} / >
    );
  }, null);

  return element;
}
Copy the code

As you can see, with React.Context, version v6 wraps a layer of RouteContext around each route element rendering.

Take a more convenient route nesting:

In v6, useRouteMatch is omitted in favor of using path as a relative path.

This is done with this scheme, because each layer of routing file wraps the Context, so each layer can get the path of the previous layer, so the stitching of routes can be done entirely internally by the framework, without requiring the user to pre-concatenate the route at call time.

Navigate, for example, is a formal input, but is also a functional input. If a relative path is passed in, the navigate is switched based on the current route.

export function useNavigate() {
  let { history, pending } = React.useContext(LocationContext);
  let { pathname } = React.useContext(RouteContext);

  let navigate = React.useCallback(
    (to, { replace, state } = {}) = > {
      if (typeof to === "number") {
        history.go(to);
      } else {
        let relativeTo = resolveLocation(to, pathname);

        letmethod = !! replace || pending ?"replace" : "push";
        history[method](relativeTo, state);
      }
    },
    [history, pending, pathname]
  );

  return navigate;
}
Copy the code

RouteContext is used to obtain the current pathname, and resolveLocation is used to concatenate the path between to and pathname. The PathName is provided through routecontext.provider.

Use multiple Context providers wisely

Most of the time we useContext on a Provider, multiple useContext level. This is the most basic usage of Context, but after reading the React Router v6 article, we can explore more uses of Context: Multi-level Context providers.

Although the Context Provider has multiple layers that take the most recent override principle, this is not just an error-avoiding feature, we can use this feature to implement improvements like React Router V6.

For example, when implementing the render engine, each component has an ID, but this ID is not visible on the component props:

const Input = (a)= > {
  // The Input component automatically generates an ID on the canvas, but this ID cannot be obtained by props
};
Copy the code

If we allow a child element to be created inside the Input component and want the child element’s ID to be derived from the Input, we might want the user to do something like this:

const Input = ({ id }) = > {
  return <ComponentLoader id={id+"1} "/ >;
};
Copy the code

There are two problems with this:

  1. Exposing the ID to the Input component violates the simplicity of the previous design.
  2. The component needs to assemble the ID, which is cumbersome.

The problems encountered here are the same as with the React Router. We could simplify the code as follows, but would the functionality remain the same?

const Input = (a)= > {
  return <ComponentLoader id="1" />;
};
Copy the code

And the answer is yes, we can do this with Context. The key point is that render the Input but the component container needs to wrap a Provider around it:

const ComponentLoader = ({ id, element }) => {
  <Context.Provider value={{ id }}>{element}</Context.Provider>;
};
Copy the code

For internal components, useContext gets a different ID at different levels, which is exactly what we want:

const ComponentLoader = ({id,element}) => {
  const { id: parentId } = useContext(Context)

  <Context.Provider value={{ id: parentId + id }}>
    {element}
  </Context.Provider>
}
Copy the code

So the
we call inside the Input is actually the actual id of the concatenation is 01, and this is completely thrown out to the external engine layer, the user does not need to manually concatenate.

4 summarizes

React Router V6 has been refactored entirely with Hooks, making the code much simpler and easier to use, and allowing for a quick wave of updates when it is released.

In addition to these optimizations made by React Router V6, we have found more clever uses of Context from the source code. Hopefully, this method will help you apply it to more complex projects.

React Router v6 · Issue #241 · dt-fe/weekly

If you’d like to participate in the discussion, pleaseClick here to, with a new theme every week, released on weekends or Mondays. Front end Intensive Reading – Helps you filter the right content.

Pay attention to the front end of intensive reading wechat public account

Copyright Notice: Freely reproduced – Non-commercial – Non-derivative – Remain signed (Creative Commons 3.0 License)