preface
The React Router has become a necessity during the React stack learning process. It is the complete React routing solution.
In this article, readers are expected to learn not only how to use the React Router, but also how to implement it.
Therefore, the author plans to explain it in the following two sections:
1. React Router Theory: Explain the concepts of the React Router in detail.
Implement a simple Router version step by step.
React Router Theory
What is front-end routing
Change and listen to the URL to make a DOM node display the corresponding view.
Based on the sample
import React from "react";
import {
BrowserRouter as Router,
Switch,
Route,
Link,
useRouteMatch,
useParams
} from "react-router-dom";
export default function App() {
return (
<Router>
<div>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
<li>
<Link to="/topics">Topics</Link>
</li>
</ul>
<Switch>
<Route path="/about">
<About />
</Route>
<Route path="/topics">
<Topics />
</Route>
<Route path="/">
<Home />
</Route>
</Switch>
</div>
</Router>
);
}
function Home() {
return <h2>Home</h2>;
}
function About() {
return <h2>About</h2>;
}
function Topics() {
let match = useRouteMatch();
return (
<div>
<h2>Topics</h2>
<ul>
<li>
<Link to={` ${match.url} /components`} >Components</Link>
</li>
<li>
<Link to={` ${match.url} /props-v-state`} >
Props v. State
</Link>
</li>
</ul>
<Switch>
<Route path={` ${match.path} /:topicId`} >
<Topic />
</Route>
<Route path={match.path}>
<h3>Please select a topic.</h3>
</Route>
</Switch>
</div>
);
}
function Topic() {
let { topicId } = useParams();
return <h3>Requested topic ID: {topicId}</h3>;
}
Copy the code
This is an example of nested routines, and the end result looks like this:
As the URL changes, the front end route displays the corresponding component and does not trigger the whole page refresh. This is the front end route, do not think of it too complicated.
In this example, we see a lot of new faces: Router, Switch, Route, and so on. The React Router API is used in the React Router.
The router
BrowserRouter
The core of every React Router application should be the Router component. For Web projects, react-router-dom provides BrowserRouter and HashRouter routers.
BrowserRouter uses a regular path, http://baidu.com/path/a1, which is implemented through JavaScript’s history object, which we’ll explain in more detail in The Principles section.
HashRouter
The HashRouter uses a hash path, http://baidu.com/#/path/a1.
Hash = ‘foo’ to change the path from baidu.com to baidu.com/#foo.
The window.addeventListener (‘hashchange’) event is used to listen for changes to the hash value.
Routing matcher
There are two types of routing matching components: Switch and Route. When the Switch component is rendered, its Route child element is searched, which renders the Route matched by the first path found and ignores all other routes.
Route
Define the relative paths of the presentation components and render the corresponding components when the paths match.
<Route path="/contact">
<Contacts />
</Route>
Copy the code
Switch
Find the Route that matches the first path, and otherwise render all Route components that match the path if there is no Switch.
<div>
<Route path="/about">
<About />
</Route>
<Route path="/:user">
<User />
</Route>
<Route>
<NoMatch />
</Route>
</div>
Copy the code
When the path matches /about, all three components match and are shown. But what if you only want to match one component?
<Switch>
<Route exact path="/">
<Home />
</Route>
<Route path="/about">
<About />
</Route>
<Route path="/:user">
<User />
</Route>
<Route>
<NoMatch />
</Route>
</Switch>
Copy the code
The Switch component is also similar in concept to the JavaScript Switch function.
navigation
The Link to Redirect
Jump and redirect, this is very simple.
<Link to="/">Home</Link> /login"/> // Redirect to the login sectionCopy the code
Hook
The React Router comes with hooks that let you access the Router state and perform route jumps from within the component.
useHistory
: gethistory
Object is used to adjust routes. It is equivalent toLink
Component functions.useLocation
: getlocation
Object, as long asURL
Change, and it returns a new location.useParams
: getURL
Matches parameter values, such as route definitions/blog/:slug
When the access/blog/bar
When, throughlet { slug } = useParams();
slug
The value is equal tobar
。
These are some of the core concepts of the React Router, and they’re pretty straightforward. Let’s implement a Router together.
The React principle of the Router
There are usually two ways to implement browser-side routing, hash and the history object provided by H5, which will be used in this article.
history
pushState()
history.pushState(state, title[, url])
Copy the code
state
Represents the state object, which allows us to create its own state for each route;title
, has no effect for the time being;url
,URL
The path.
Example:
history.pushState({foo:'foo'}, ' ', foo) can change example.com to example.com/foo.Copy the code
A similar method is replaceState(), which is very similar to pushState() except that the former replaces the history and the latter adds the history.
History does not cause the browser to reload
Using location.href = ‘baidu.com/foo’ would cause the browser to reload the page and request the server, but the magic of history.pushState() is that it can change the URL without reloading the page, It is entirely up to the user to decide how to handle this URL change, which is a necessary feature for implementing front-end routing.
After the history route is refreshed, 404 is displayed
The server does not map the url to baidu.com/foo, so it returns 404. The server returns index.html for pages that it does not recognize. The home page, which contains the JavaScript code associated with front-end routing, will load your front-end routing configuration table, and even though the server gives you the home page file, but your URL is baidu.com/foo, the front-end routing will load the view corresponding to the path /foo. Solved 404 problem perfectly.
The history library
The React Router uses github.com/ReactTraini… This library.
Its basic use is as follows:
import { createBrowserHistory } from 'history';
// Create a history object
const history = createBrowserHistory();
// Get the location object
const location = history.location;
// Listen on the history object
const unlisten = history.listen((location, action) = > {
console.log(action, location.pathname, location.state);
});
// Jumps to the specified address and pushes a new record into the browser session history stack.
history.push('/home', { some: 'state' });
Copy the code
A simple implementation of the Listen method is as follows:
let listeners = [];
function listen(fn) {
listeners.push(fn);
}
Copy the code
2. Listeners are simple arrays of functions that are pushed into them whenever they are listened on.
A simple implementation of the push method is as follows:
function push(to, state) {
window.history.pushState(state, ' ', to);
listeners.forEach(fn= > fn(location));
}
Copy the code
Resolution:
- The bottom line is through calls
history.pushState
Methods to changeURL
; - The subscription function is then executed (essentially a simple publish-subscribe process).
The path – to – the regexp library
Another important tool library used by the React Router is path-to-regexp, which handles url addresses and parameters to get the data you want.
Example:
import { pathToRegexp } from "path-to-regexp";
const regexp = pathToRegexp("/foo/:bar"); // get regexp = /^\/foo\/([^\/]+?) / /? $/iRegexp. Exec (STR);Copy the code
With this knowledge in hand, we’re ready to implement a simple React Router.
imitation-react-router
Let’s review the React Router components:
BrowserRouter
To providehistory
Object and passContext
Deliver to all descendant components;Switch
To find the first path matchingRoute
;Route
whenpath
When matched, the corresponding component is rendered;Link
Intercept,a
The TAB clicks on the event and passeshistory.push
Methods to changeURL
。
Before writing the React component, we implement two utility methods: Context and matchPath.
Context.js
Initialize a Context.
import React from "react";
const Context = React.createContext(null);
export default Context;
Copy the code
matchPath.js
Determine whether the route matches the path, that is, /topics/: ID matches /topics/components.
import { pathToRegexp } from "path-to-regexp";
export function matchPath(pathname, path) {
const keys = [];
const regexp = pathToRegexp(path, keys);/ / {1}
const match = regexp.exec(pathname); / / {2}
if(! match)return null;
const values = match.slice(1);
return {
params: keys.reduce((memo, key, index) = > {
memo[key.name] = values[index];
returnmemo; }, {}}; }Copy the code
Resolution:
- {1}, through
pathToRegexp
Method, based on the path passed in for example/topics/:id
And generate the corresponding matching re/^\/topics(? : \ / ([^ # \ \ /?] +? ) / # \ \ /? ? $/i
; - {2}, to perform the regular
exec
Match the actual path passed, for example/topics/components
; - And finally print one
params
Object, which contains something like this:
params:{
"id": "components"
}
Copy the code
BrowserRouter
Create the history object and use the Context to deliver it to all descendant nodes. Let’s see how it is implemented.
import React, { useState, useCallback } from "react";
import { history } from ".. /lib-history/history";
import Context from "./Context";
const BrowserRouter = props= > {
/ / {1}
const [location, setLocation] = useState(history.location);
/ / {2}
const computeRootMatch = useCallback(pathname= > {
return { path: "/".url: "/".params: {}, isExact: pathname === "/"}; } []);/ / {3}
history.listen(location= > {
setLocation(location);
});
/ / {4}
return (
<Context.Provider
value={{ history.location.match: computeRootMatch(location.pathname)}} >
{props.children}
</Context.Provider>
);
};
export default BrowserRouter;
Copy the code
Resolution:
- Based on {1}
history.location
Create state; - {2}, to initialize the root path
match
Object; - {3}, listening
history
Object that is executed when it has changedlocation
State to refresh all components; - {4}, through the above creation
Context
,History, location, match
Object asvalue
Value.
Route
There are two uses, one is included with the Switch, and one is used independently.
import React, { useContext } from "react";
import Context from "./Context";
import { matchPath } from "./matchPath";
const Route = props= > {
/ / {1}
const { location, history } = useContext(Context);
/ / {2}
const match = props.computedMatch
? props.computedMatch
: matchPath(location.pathname, props.path);
return (
<Context.Provider value={{... match,location,history}}>
{/* 3 */}
{match ? props.children : null}
</Context.Provider>
);
};
export default Route;
Copy the code
Resolution:
- {1} to use
useContext Hook
To obtaincontext
The values; - {2},
props.computedMatch
Is included in theSwtich
Is passed in, so it checks whether the property exists and if so, it is usedSwitch
incomingmatch
Object, if noneprops.computedMatch
Said,Route
If it is used independently, use itmatchPath
To do the matching, get the matching results; - {3}, according to the matching results to determine whether to render the component.
Link
The main purpose of this component is to intercept the click event of the A tag.
import React, { useContext, useCallback } from "react";
import Context from "./Context";
const Link = ({ to, children }) = > {
const { history } = useContext(Context);
const handleOnClick = useCallback(
event= > {
event.preventDefault();
history.push(to);
},
[history, to]
);
return (
<a href={to} onClick={handleOnClick}>
{children}
</a>
);
};
export default Link;
Copy the code
Switch
Find the Route that matches the first path.
import React, { useContext } from "react";
import Context from "./Context";
import { matchPath } from "./matchPath";
const Switch = props= > {
const context = useContext(Context);
const location = context.location;
let element,
match = null;
/ / {1}
React.Children.forEach(props.children, child= > {
if (match === null && React.isValidElement(child)) {
element = child;
/ / {2}
const path = child.props.path;
/ / {3}match = path ? matchPath(location.pathname, child.props.path) : context.match; }});/ / {4}
return match
? React.cloneElement(element, { location, computedMatch: match })
: null;
};
export default Switch;
Copy the code
Resolution:
- {1}, through
React.Children
Access to theSwitch
All descendant elements under the component and iterate through them; - {2}, access to
Route
的path
The attribute value; - {3}, by judgment
path
If yes, the path is matchedmatchPath
, is used if there is no valueBrowserRouter
Incoming to proceedmatch
Object, which means if<Route><Foo /></Route>
If no path is set, the default root path will be matched. - {4}, if a match is matched, pass
React.cloneElement
Method returns a new clone element and sets the{ location, computedMatch: match }
As aprops
The incoming.
Hooks
import React from "react";
import Context from "./Context";
export function useParams() {
return React.useContext(Context).params;
}
export function useHistory() {
return React.useContext(Context).history;
}
export function useLocation() {
return React.useContext(Context).location;
}
Copy the code
Hook implementation is relatively simple, mainly through the Context to get the corresponding object, back out.
The overall implementation does not consider too many boundary conditions, mainly in order to quickly understand the Router principle. Finally, a simple example:
import React from "react";
import {
BrowserRouter as Router,
Switch,
Route,
Link,
useParams,
} from "./imitation-react-router";
export default function App() {
return (
<Router>
<div>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
<li>
<Link to="/topics">Topics</Link>
</li>
<li>
<Link to={` /topics/components`} >Components</Link>
</li>
</ul>
<Switch>
<Route path="/about">
<About />
</Route>
<Route path={` /topics/:id`} >
<Topic />
</Route>
<Route path="/home">
<Home />
</Route>
</Switch>
</div>
</Router>
);
}
function Home() {
return <h2>Home</h2>;
}
function About() {
return <h2>About</h2>;
}
function Topic() {
let { id } = useParams();
return <h3>Requested topic ID: {id}</h3>;
}
Copy the code
A simple case can run successfully.
conclusion
The React Router itself is not complicated, but you can learn a lot about it by learning its source code. If you compare the implementation of Redux, you’ll see that the global object is delivered by Context. Both use publish subscriptions to notify components of updates.
Click to view the >>> code hosting address
Please give a like if you like this article!