A preface
React-router v6 is now available in version 6. Many students may find that V6 is completely different from v5, because recently they came into contact with the new project of React and used react-Router V6. Is this the same router I know? There are major changes from API to principle, so today we will take a look at the changes in the new routing.
My advice for this change is to try a new version of Rouer for a new project, but not to upgrade v6 for an old project because it will require a lot of functionality changes, and if you use third-party libraries that depend on the Router, they may fail. As a result, some third party libraries that rely on react-Router will also need to be updated to accommodate version v6, such as my react-Keepalive-router, which cache pages, will also get a major update.
Through this chapter, you will learn the following:
- Differences in routing, usage, and API between the new version and the old version.
- The new routing components Router, Routes, and Route work.
- Principles of Outlet components.
- UseRoutes principle.
Let’s begin today’s Router V6 learning tour. Send rose πΉ, the hand has lingering fragrance, hope to see the feel of the harvest of the students, can give the author praise β pay attention to a wave, in order to encourage me to continue to create front-end hard text.
Ii Basic Use
First let’s look at the changes in V6 from the way routing is used, or an example of a scenario. For example, there is the following routing structure:
As shown in the figure above, the page is divided into a simple 2-level routing structure:
- The first level page has
home
Page,list
Page, andchildren
Page. - The second level page is the child path of the Children page, including:
child1
εchild2
.
Let’s take a look at how the old and new versions of routing are used differently.
1 Route of the old version
Configure routes of the earlier version
Entry file -> Level 1 Routing
const index = () = > {
return <div className="page" >
<div className="content" >
<BrowserRouter>
<Menus />
<Switch>
<Route component={Children}/ *childrenComponent * /path="/children"
></Route>
<Route component={Home}/ *homeComponent * /path={'/home'}
></Route>
<Route component={List}/ *listComponent * /path="/list"
></Route>
</Switch>
</BrowserRouter>
</div>
</div>
}
Copy the code
The preceding is the case of the configured level-1 route. Let’s look at the general distribution of functional responsibilities:
BrowserRouter
: Passes through the history libraryhistory
Object,location
Object.Switch
: Matches a unique routeRoute
To display the correct routing components.Route
The view hosts the container and controls renderingUI
Components.
The above is the configuration of the primary route and corresponding components. Next, let’s look at the configuration of the secondary route. The secondary route is configured in Children:
function Children (){
return <div>Here is the Children page<Menus />
<Route component={Child1}
path="/children/child1"
/>
<Route component={Child2}
path="/children/child2"
/>
</div>
}
Copy the code
- You can see in the
Children
There,Child1
εChild2
Two components.
Take a look at the overall effect:
The overall routing hierarchy structure is shown below (focusing on the differences with v6’s overall design) :
Route status and page redirect
V5 can obtain the route status in the following ways
- Props + Route: The UI component supported by Route can obtain the routing state through props. If you want to pass the routing state to descendant components, you can pass the routing state layer by layer through props.
withRouter
: withRouter is a higher-order component HOC, because the default is only HOCRoute
The component of the package can only get the routing state. If the current non-routed component wants to get the state, it can get it through the withRouter packagehistory
οΌlocation
And other information.useHistory
: Function components can passuseHistory
To obtainhistory
Object.useLocation
: Function components can passuseLocation
To obtainlocation
Object.
The V5 uses the following methods to redirect routes
Route state acquisition was introduced above, and another scenario is route switching, so V5 mainly changes the route in two ways:
- through
react-router-dom
The built-inLink
.NavLink
Component to implement route jumps. - through
history
Object under the route jump method, such as push, to achieve the route jump.
Overall architecture design
Routing state transfer
In React, routing state is transmitted through a Context. As we all know, in React, Context is a great way to transmit state. In Router, Context is also transmitted through a Router. In React-Router V5.1.0 and earlier, information such as the history and Location object is passed through a RouterContext.
In V5.2.0 to new V5 React-Router, history state is stored separately by HistoryContext in addition to RouterContext.
The overall design of routing module
Let’s take a look at the overall design of v5’s React-Router:
The above is the module design of the whole React – Router V5.
2 V6 Router
Next, we tried the React – Router V6 version. Use V6 to achieve the above functions.
Route configuration of the new version
Entry File -> Overall Routing configuration
import { Routes , Route , Outlet } from 'react-router
import { BrowserRouter } from 'react-router-dom' const index = () => { return
} path="/home" >
} path="/list" >
} path="/children" >
} path="/children/child1" >
} path="/children/child2" >
}Copy the code
As mentioned above, we also implemented nested secondary routing with the V6 router. Through the above code, we can conclude:
-
- In v6
BrowserRouter
εHashRouter
It’s still at the top of the application. Provides core objects such as history.
- In v6
-
- In the new version of the router, there is no matching unique route
Switch
Component, insteadRoutes
Component, but we cannot use Routes as a substitute for Switch. In the new architecture, Routes plays an important role in theReact-router Routing principleIn this article, we explained that the Switch can match a unique Route component to render based on the current Route path. The Switch itself can be discarded, but Routes play an important role in the new version. For example, Route can be used directly without Switch in V5, but Route can be used in V6. The outer layer must be added with Routes component, that is, the combination of Routes -> Route.
- In the new version of the router, there is no matching unique route
If there is no Routes around the Route, an error is reported. Such as the following
This is something that you should pay attention to when developing.
-
- For newer versions of routing, the nested routing structure will be clearer. For example, in the old version of routing, configuring the secondary routing needs to be configured in the business component, as in the first example, we need to configure in the business component
Children
Configure the secondary route in the component. In V6, however, there has been an improvement in configuring child routes, which can be written directly to the Route component, such as GeneralChild1
εChild2
I wrote it right there/children
Where will the child routes be renderedLayout
The component. Take a look at how Layout renders the child routing components.
- For newer versions of routing, the nested routing structure will be clearer. For example, in the old version of routing, configuring the secondary routing needs to be configured in the business component, as in the first example, we need to configure in the business component
Layout -> Render secondary routing
function Container(){
return <div> <Outlet/></div>
}
/* Subroute menu */
function Menus1(){
return <div>
<Link to={'/children/child1'} > child1 </Link>
<Link to={'/children/child2'} > child2 </Link>
</div>
}
function Layout(){
return <div>Here is the Children page<Menus1 />
<Container />
</div>
}
Copy the code
- As you can see, Layout does not render a subpath directly, but only one
Container
The Container uses the V6 Router in the ContainerOutlet
. The Outlet is where the child routes are actually rendered, which is Child1 and Child2. The Outlet here is more like an identity card, proving that this is where the real routing component is mounted, and it is not affected by the component hierarchy (as you can see directly from above, the Outlet is not inside the Layout, but in the Container). This way, it is clearer and more flexible. The ability to render components to any node in the subcomponent tree.
So to summarize the routing structure diagram, see the following:
From the above comparison, you can see the general differences between V6 and V5. Here is a summary of the functional aspects:
-
- Newer versions of the Router do not exist
Switch
Component, replaced by Routes, but in terms of functionRoutes
Is central, plays an indispensable role. The route of the old version can be used independently. The route of the new version must be used together with Routes.
- Newer versions of the Router do not exist
-
- The new version of routing introduces the Outlet placeholder function, which facilitates the configuration of the routing structure. Unlike the old version of routing, subroutes are configured in specific service components, which is clearer and more flexible.
Let’s take a look at the other features of V6.
Route status and page redirect
Obtain route status and redirect the page
-
- State to obtain: For route state location, use custom hooks
useLocation
. The location which stores the hash | key | pathname | search | state, etc.
- State to obtain: For route state location, use custom hooks
-
- Routing hop: New routing is provided
useNavigate
To redirect routes. Refer to the following code for specific usage:
- Routing hop: New routing is provided
function Home (){
const navigate = useNavigate()
return <div>
<button onClick={()= >Navigate ('/list',{state:'alien'})} > The jump list page</button>
</div>
}
Copy the code
Navigate: The first parameter is the forward path, and the second parameter is the route status information that is described, which can convey information such as state.
-
- Dynamic routing: The new version implements dynamic routing and is flexible. You can use useParams to obtain dynamic routing information from urls. For example:
Configuration:
<Route element={<List/>} path="/list/:id"></Route>
Copy the code
Switch to the dynamic routing page:
<button onClick={() = >{ navigate('/list/1'})}} > Jump list page </button>Copy the code
UseParams Obtains dynamic route parameters
function List(){
const params = useParams()
console.log(params,'params') // {id: '1'} 'params'
return <div>
let us learn React !
</div>
}
Copy the code
-
- Url parameter information acquisition:A new version of the route is provided
useSearchParams
canTo obtain ο½ Set up theThe url parameter. Here’s an example:
- Url parameter information acquisition:A new version of the route is provided
function Index(){
const [ getParams ,setParam] = useSearchParams() // The first parameter getParams gets the url information such as param, and the second parameter setParam sets the URL information such as param.
const name = getParams.getAll('name')
console.log('name',name)
return <div>
hello,world
<button onClick={()= >{setParam({name:'alien', age: 29}) // can set the url param information}} > setParam</button>
</div>
}
Copy the code
UseSearchParams returns an array.
- The first entry in array 1,
getParams
Obtain URL parameter information. - The second entry in array 2,
setParam
Example Set URL parameters.
Take a look at the demo:
-
- Configuration is more flexible.In version v5, you can configure routing components using an additional routing plug-in called Options
react-router-config
In therenderRoutes
Methods. Custom hooks are provided in version v6useRoutes
The yield configuration is more flexible. Let’s see how it works.
- Configuration is more flexible.In version v5, you can configure routing components using an additional routing plug-in called Options
const routeConfig = [
{
path:'/home'.element:<Home />
},
{
path:'/list/:id'.element:<List />
},
{
path:'/children'.element:<Layout />,
children:[
{ path:'/children/child1' , element: <Child1/> },
{ path:'/children/child2' , element: <Child2/>}}]]const Index = () = > {
const element = useRoutes(routeConfig)
return <div className="page" >
<div className="content" >
<Menus />
{element}
</div>
</div>
}
const App = () = > <BrowserRouter><Index /></BrowserRouter>
Copy the code
The above makes the structure clearer and configuration more flexible.
-
- In addition, V6 provides hooks for several other functions, which will not be covered here. If you are interested, check out the official documentation, Portal.
Overall architecture design
Let’s take a look at the overall v6 design:
- As you can see in the figure above, the new version v6 has fully embraced hooks.
- A number of contexts are passed, such as navigate (functionally the old version of history)
) NavigationContext
Object, the LocationContext object that passes location, etc.
Three-principle analysis
The above describes the differences in usage between V5 and V6 versions of routing. Next, let’s focus on how the new Route works. And how it differs from the old version.
1 New Route design
In the old version of routing, the core component is Route. As described in the previous article on the Principle of routing, Route is consumed by context internally. When the Route changes, the Route consuming context is re-rendered. So the core component to actually match and mount is Route.
In the new version of Route, the logic for routing updates, Route matching, and rendering actual page components is handed over to Routes, and a branch is added. The new version of the routing structure can be interpreted as a hierarchical tree structure, that is, when the route changes, the Routes will be found in the routing structure tree, the need to render branch. The main purpose of the Route component at this point is just to form each node in the routing tree structure, but there is no actual rendering of the page.
The routing of the new version decouples routes from service components. The configuration of routes is handled through the outer routing structure tree instead of inside the service components to be specified. For views it is passed layer by layer through OutletContext, so let’s look at the details.
2 the outer container, update the source BrowserRouter | HashRouter | the Router
In the new version of routing, the outer Router components are different from those in the old version. Using BrowserRouter as an example, take a look at the older version.
Older versions of BrowserRouter
import { createBrowserHistory as createHistory } from "history";
class BrowserRouter extends React.Component {
history = createHistory(this.props)
render() {
return <Router history={this.history} children={this.props.children} />; }}Copy the code
- Older versions of BrowserRouter do just that
createHistory
createhistory
Object that is then passed to the Router component.
Now, the new version of BrowserRouter, what does it do?
react-router-dom/index.tsx
export function BrowserRouter({
basename,
children,
window
}: BrowserRouterProps) {
/* Save the history object with useRef */
let historyRef = React.useRef<BrowserHistory>();
if (historyRef.current == null) {
historyRef.current = createBrowserHistory({ window });
}
let history = historyRef.current;
let [state, setState] = React.useState({
action: history.action,
location: history.location
});
/* history change, notification update. * /
React.useLayoutEffect(() = > history.listen(setState), [history]);
return (
<Router
basename={basename}
children={children}
location={state.location}
navigationType={state.action}
navigator={history}
/>
);
}
Copy the code
The new version of BrowserRouter does the following:
- through
createBrowserHistory
createhistory
Object and passuseRef
Save the history object. - through
useLayoutEffect
To listen tohistory
Changes when history changes (browser input, get a tag jump, API jump, etc.). Distribute updates and render the entire Router tree.This is different from older versions where the update component listens for route changes in the Router. - One other thing to note, in the old version, there was a
history
Object concept, which is called in the new versionnavigator
γ
Let’s look at what the new version of the Router does.
react-router/index.tsx
function Router({basename,children,location:locationProp,navigator}){
/* Create navigationContext to hold basename, navigator, etc. * /
let navigationContext = React.useMemo(
() = > ({ basename, navigator, static: staticProp }),
[basename, navigator, staticProp]
);
/* Struct the state inside the location */
const { pathname, search, hash, state, key } = locationProp
/* Create a locationContext object that holds pathName, state, etc. * /
let location = React.useMemo(() = > {
/* .... */
return { pathname, search, hash, state, key }
},[basename, pathname, search, hash, state, key])
/* Pass navigationContext and locationContext, respectively, through context
return (
<NavigationContext.Provider value={navigationContext}>
<LocationContext.Provider
children={children}
value={{ location.navigationType}} / >
</NavigationContext.Provider>)}Copy the code
The Router plays the following roles in the new routing:
- The navigator object and the location object of routing information are derived from useMemo. Pass them through the React Context.
- When the route changes, the
BrowserRouter
Change the location via useState, so when the location changes,LocationContext
Changes, the consuming LocationContext is updated.
3 Principles In depth, use the Routes and branch concepts
Using BrowserRouter as an example, we showed what the outer container does. Let’s dig deeper and take a look at what happens inside Routes and how the routing hierarchy is formed. And routing to the process of rendering the corresponding page.
Take the following examples for reference:
<Routes>
<Route element={<Home />} path="/home" />
<Route element={<List/>} path="/list" />
<Route element={<Layout/>} path="/children" >
<Route element={<Child1/>} path="/children/child1" />
<Route element={<Child2/>} path="/children/child2" />
</Route>
</Routes>
Copy the code
Let’s think about it with two questions.
- If the current
pathname
δΈΊ/home
, so how does the entire route show the Home component? - If the route is switched to
/children/child1
What is the flow from page update to rendering? And how inLayout
Internally renderedChild1
γ
Route and Routes form the routing structure
As mentioned above, the new Route must be used in conjunction with Routes. What does the new version of Route do when the old version is critical for matching and updating containers?
react-router/index.tsx
function Route(_props){
invariant(
false.`A <Route> is only ever to be used as the child of <Routes> element, ` +
`never rendered directly. Please wrap your <Route> in a <Routes>.`
);
}
Copy the code
For those of you who have just seen Route, there will be no logic, just a random cue. This may surprise many students, but the Route component is not a regular component and can be understood as an empty function.
In fact, the source of all processing is the Routes component. Its function is to match the correct render branch branch based on the route changes.
Then Routes are the focus of our research.
Routes and useRoutes
Let’s start with the implementation of Routes:
react-router/index.tsx
export function Routes({children,location }) {
return useRoutes(createRoutesFromChildren(children), location);
}
Copy the code
- use
<Routes />
When useRoutes returns the React Element object, useRoutes is used as a viewhooks
. Routes essentially uses useRoutes.
If you can use useRoutes, you can change the route configuration structure directly to an element structure, and you are responsible for displaying the routing components that match the route, then useRoutes is the core of the routing architecture.
So before we get to useRoutes let’s figure out what does createRoutesFromChildren do?
react-router/index.tsx -> createRoutesFromChildren
function createRoutesFromChildren(children) { /* From a hierarchy nested structure */
let routes = [];
Children.forEach(children, element= > {
/* Omit element validation, and flmanagement processing logic */
let route = {
caseSensitive: element.props.caseSensitive, // Case sensitive
element: element.props.element, / / element object
index: element.props.index, / / index index
path: element.props.path // Route path
};
if (element.props.children) {
route.children = createRoutesFromChildren(element.props.children);
}
routes.push(route);
});
return routes;
}
Copy the code
- CreateRoutesFromChildren internally assigns the Route component to the structure via react.children. ForEach, and internally calls the recursive, deeply recursive Children structure.
CreateRoutesFromChildren can turn react Element objects of type
into normal Route object structures. We’ve already said that Route is essentially an empty function and is not actually mounted, so it is transformed by createRoutesFromChildren handling.
For example, the following structure:
<Routes>
<Route element={<Home />} path="/home" />
<Route element={<List/>} path="/list" />
<Route element={<Layout/>} path="/children" >
<Route element={<Child1/>} path="/children/child1" />
<Route element={<Child2/>} path="/children/child2" />
</Route>
</Routes>
Copy the code
Element is converted to the following structure:
The next thing exposed is useRoute, which seems to be involved in everything from routing mount to switching routes for re-rendering. So let’s focus on the custom hooks.
react-router/useRoutes
function useRoutes(routes, locationArg) {
let locationFromContext = useLocation();
/ *TODO:Stage 1: Calculate pathName */
/ /... The code is omitted
/ *TODO:Phase 2: Find the matching route branch */
let matches = matchRoutes(routes, {
pathname: remainingPathname
});
console.log('----match-----',matches)
/ *TODO:Stage 3: Render the corresponding routing component */
return _renderMatches(matches && matches.map(match= > Object.assign({}, match, {
params: Object.assign({}, parentParams, match.params),
pathname: joinPaths([parentPathnameBase, match.pathname]),
pathnameBase: match.pathnameBase === "/" ? parentPathnameBase : joinPaths([parentPathnameBase, match.pathnameBase])
})), parentMatches);
}
Copy the code
This code is part of the v6 routing core, which I’ve broken down into three stages to help you understand.
- In the first stage, the corresponding Pathname is generatedThe preceding example is used again, for example, route switchover
/children/child1
, so pathname is/children/child1
. - Phase two, pass
matchRoutes
To find the matching route branch.What is a matching route branch, such as switching routes to/children/child1
Root -> children -> child1. Let’s print matches and look at the data structure.
- The other thing is that
useRoutes
Internal useuseLocation
. When the Location object changes, useRoutes will re-render.
Matches is the flattened route structure. It is an array structure. Therefore, index 0 is the layer 1 route and index 1 is the layer 2 route. So let’s look at the implementation of matchRoutes.
MatchRoutes and _renderMatches render route branches
react-router/index.tsx -> matchRoutes
function matchRoutes(routes,locationArg,basename){
/* Flat routes structure */
let branches = flattenRoutes(routes);
/* Sort route */
rankRouteBranches(branches);
let matches = null;
/* Through matchRouteBranch */
for (let i = 0; matches == null && i < branches.length; ++i) {
matches = matchRouteBranch(branches[i], pathname);
}
return matches;
}
Copy the code
- The first flattening of the array on the grounder was done on routes. The flattening looked something like this.
There is a routesMeta attribute in flattened branches, storing the information of each route. For example, /children/child1 contains two layers of routes in essence. The first layer is /children and the second layer is /child1;
-
Then adjust the order of routes by rankRouteBranches.
-
Finally, the for loop and matchRouteBranch find the route branch to render, terminating the loop if matches is not null. The principle of matchRouteBranch is not covered for space reasons, but its main function is to find routes under the routesMeta to be rendered by pathname. The final matches structure is then formed.
Matches, which we know holds the route to be rendered. Then the next step is to render the route, render the corresponding page. So that’s basically what _renderMatches does, so what does this function do?
react-router/index.tsx -> _renderMatches
function _renderMatches(matches, parentMatches) {
if (parentMatches === void 0) {
parentMatches = [];
}
if (matches == null) return null;
return matches.reduceRight((outlet, match, index) = > {
/* Use the element of the previous item as an outlet to the next item */
return createElement(RouteContext.Provider, {
children: match.route.element ! = =undefined ? match.route.element : /*#__PURE__*/createElement(Outlet, null),
value: {
outlet,
matches: parentMatches.concat(matches.slice(0, index + 1))}}); },null);
}
Copy the code
This code is very subtle and contains a large amount of information. It forms the React structure Elmenet through reduceRight, which solves three problems:
-
- The first layer of route page is rendered.
-
- How outlets are rendered as child routes.
-
- How the routing state is transmitted.
First of all, we know that reduceRight is traversed from right to left. As mentioned before, the match structure is root -> children -> child1. ReduceRight takes the content returned by the former item as an outlet of the latter item. So the match structure above will be treated like this.
- 1 first wraps child1 with the provider, so that the content that child1 really needs to render will be treated as the children of the provider. Finally, the current provider will be returned to Child1. So the first level outlet is null.
- 2 Then the provider returned by the first layer, as an outlet to the second layer, is passed through the outlet property in the value of the second layer provider. The Layout component is then returned as children.
- Child1 is not rendered directly, but is passed as a Provider property.
So child1 is rendered as an Outlet placeholder in a Container. So let’s think about what outlets do, we’ll probably get our Outlet from the first layer provider using useContext and then we’ll render the provider for Child1, Child1 is rendered as children. Let’s see if this is true.
react-router/index.tsx -> Outlet
export function Outlet(props: OutletProps) :React.ReactElement | null {
return useOutlet(props.context);
}
Copy the code
- An Outlet is essentially using useOutlet, so let’s see
useOutlet
.
react-router/index.tsx -> useOutlet
export function useOutlet(context? : unknown) :React.ReactElement | null {
let outlet = React.useContext(RouteContext).outlet;
if (outlet) {
return (
<OutletContext.Provider value={context}>{outlet}</OutletContext.Provider>
);
}
return outlet;
}
Copy the code
- Get the outlet above the Provider, and render the outlet, so the secondary subpath will render normally.
At this point, the entire V6 rendering philosophy is clear.
What we do in reduceRight is represented by a flow chart.
Route updates to the corresponding component rendering presentation flow
Let’s look at what happens if a jump is made from navigator, such as a home jump to a child1 component.
- Using BrowserRouter as an example, when a route is updated, the Listen event in BrowserRouter will be triggered first, and a new Location object will be formed. And then the locationContext is updated.
- UseRoutes consumes the locationContext internally, and changes to the locationContext cause useRoutes to re-execute.
- The useRoutes reexecutes, internally calling matchRoutes and _renderMatches to find the new render branch and render the corresponding page.
The entire rendering process is relatively simple and clear.
The difference between V5 and V6
What is the difference between V6 and V5?
At the component level:
- The Router Switch Route structure is used in the routing of the old version. Router -> transfer status is responsible for sending updates. Switch -> matches a unique route; Route -> Real Render routing components.
- The new version uses the Router Routes Route structure. The Router extracts a context. Routes -> create route render branch, render route; Instead of rendering actual routes, routes form a branching structure.
On the level of use:
- For nested routes of old versions, configure level-2 routes and write them in specific service components.
- In the new version of routing, the routing structure is unified in the outer layer, the routing structure is clearer, and the rendering of descendant routes is implemented through outlets, somewhat similar to vUE
view-router
. - The new version has made major changes to the API, such as changing useHistory to useNavigate, reducing some apis and adding some new ones.
On the principle level:
- The essence of routing in older versions was the Route component, which rerenders when the routing context changes and then matches to determine whether the business component is rendered or not.
- The routing essence of the new version lies in the Routes component. When the location context changes, Routes are re-rendered to re-form the rendering branch, and then outlets are passed layer by layer via provider to match rendering.
Five summarizes
This article mainly introduces the basic use of V6, principle introduction, and V5 difference, interested friends can try to use V6. The overall feeling is pretty good.
The resources
- Upgrading from v5
- The react-Router routing principle was thoroughly understood