preface
The purpose of this article is to deepen my understanding of the React-Router. However, after reading the source code, I found that the source code is not quite the same as some current articles, which may be due to the different versions. Therefore, I have analyzed the latest React-Router. Please show your mercy and give me more advice.
createBrowserHistory
This method exists in the third-party library History, which is a functional enhancement to window.history.
export function createBrowserHistory (
options: BroswerHistoryOptions = {}
) :BrowserHistory {
let { window = document.defaultView! } = options;
// Used for event listening
let listeners = createEvents<Listener>();
// You can see that the createBrowserHistory implementation uses window.history
let globalHistory = window.history;
function applyTx(nextAction: Action) {
action = nextAction;
[index, location] = getIndexAndLocation();
// When location changes, setState is applied to location within the Router source code
listeners.call({ action, location });
}
function push (.) {
// Some code is omitted.try {
globalHistory.pushState(historyState, ' ', url);
} catch (error) {
window.location.assign(url);
}
applyTx(nextAction);
}
function replace (.) {
// Some code is omitted. globalHistory.replaceState(historyState,' ', url);
applyTx(nextAction);
}
let history: BrowserHistory = {
...
// Add listen to window.history
// Listen for window.location changes
// When window.location changes, the corresponding callback function is executed
listen(listener) {
returnlisteners.push(listener); },... };return history;
}
Copy the code
As you can see, BrowserHistory’s push and replace actually use HTML5’s pushState and replaceState apis. Neither of these apis causes a page refresh, but changes the route. So this also makes it possible to have a single page application.
Of course, the core is the applyTx method. When we use push or replace, applyTx is called. This method is used to call the callback function that listens to location (see the Router source below).
createHashHistory
PushState and replaceState are essentially pushState and replaceState for route changes, but there are some additional actions to monitor and change routes.
export function createHashHistory(
options: HashHistoryOptions = {}
) :HashHistory {
Unlike createBrowserHistory, createHashHistory also listens for the Hashchange event
window.addEventListener('hashchange'.() = >{... });function createHref(to: To) {
// Note that hashHistory is just an extra '#' when creating a route
return getBaseHref() + The '#' + (typeof to === 'string'? to : createPath(to)); }}Copy the code
react-router
When we write a route, we introduce BrowserRouter or HashRouter. Let’s look at how the two routers are implemented.
BrowserRouter source
import React from "react";
import { Router } from "react-router";
import { createBrowserHistory as createHistory } from "history";
class BrowserRouter extends React.Component {
// You can see that there is a third party library called history to help you create history
// createBroswerHistory is createBroswerHistory because it is BrowserRouter
history = createHistory(this.props);
render() {
// Pass history and the corresponding children (Route) to the Router
return <Router history={this.history} children={this.props.children} />; }}// Some code is omitted here.export default BrowserRouter;
Copy the code
HashRouter source
In fact, BrowserRouter is basically the same, interested can go to see the source code
Router source code implementation
class Router extends React.Component { constructor(props) { super(props); // This. State = {location: // this. State = {localtion: props.history.location }; this._isMounted = false; this._pendingLocation = null; <Redirect /> <Redirect /> If (! Props. StaticContext) {// Subscribe to location, SetState this.unlisten = props.history. Listen (location => {if (this._ismounted) {setState this.unlisten = props.history. this.setState({ location }); } else { this._pendingLocation = location; }}); } } componentDidMount() { this._isMounted = true; If (this._pendingLocation) {this.setState({location: this._pendingLocation }); }} // omit some code... < RouterContext.provider value={{history: this.props. History, location: this.state.location, match: Router.computeRootMatch(this.state.location.pathname), staticContext: This. Props. StaticContext}} > < HistoryContext. / / use the context save data Provider children = {this. Props. Children | | null} value={this.props.history} /> </RouterContext.Provider> ); }}Copy the code
Route source code implementation
Finished the Router, then there is the Route, we are in the process of writing routing, will put the < the Route > as < the Router > child of the component, in fact, of course, if you don’t do this will be an error
Basic
use
<Router>
<Route path={path} component={component} />
</Router>
Copy the code
Look at the source implementation of Route
class Route extends React.Component {
render () {
// RouterContext.consumer is used
// The Route can retrieve the data passed by the Router via the RouterContext.Provider<RouterContext.Consumer> {context => { ... / / if we are in the use of < the Route > didn't pass the location parameters, it can use the context, the location const location = this. Props. The location | | context. The location; const match = this.props.computedMatch ? this.props.computedMatch : MatchPath (location. Pathname, this.props) : // matchPath(location. Pathname, this.props) is used to compare the path attribute on the <Route> component. context.match; . / / the match is an object | null type of data / / if the match is object, will get back to the following format / / {path, url, isExact, Path return (<RouterContext.Provider value={props}> // For matched routes {props.match ? children ? typeof children === "function" ? __DEV__ ? evalChildrenDev(children, props, this.props.path) : children(props) : {Route Component ={component} /> {Route component={component} /> {Route component={component} /> {Route component={component} /> Component // omitted some code... </RouterContext.Provider> ); }} </RouterContext.Consumer> } }Copy the code
conclusion
react-router
It’s based on third-party librarieshistory
Implementation of thehistory
To the nativewindow.history
Enhanced to make the enhancedhistory
Have tolocation
Monitoring capability ofBrowserRouter
As well asHashRouter
Helped us create the correspondinghistory
And the incomingRouter
Router
对location
I’ve been listening in and willlocation
Stored in thestate
, in thelocation
When changes occursetState
<Route>
在location
If the value is changed, the value is configured based on the value itselfpath
Attribute tolocation
Render the successfully matched component.
In fact, I think react-Router is essentially a publish-subscribe application scenario. The third party library History is used to publish location changes, while the React-Router subscribes to location changes to re-render components during location changes.