Starting from the example, how is the source code handled
Ps. Source code are deleted, judgment, error and other parts of the code removed; The article is long, students who are interested in it can read it, or they can jump to the summary and directly understand the implementation principle of React Router
example
import React from 'react';
import {
BrowserRouter as Router,
Switch,
Route,
Link
} from 'react-router-dom';
export default() = > (<Router>
<div>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
</ul>
</nav>
<Switch>
<Route path="/about">
About
</Route>
<Route path="/">
Home
</Route>
</Switch>
</div>
</Router>
)
Copy the code
The React structure is
Below we read from the source code of each component
BrowserRouter
- The source code is similar to the ES6 Class inheritance of the writing method, do not pay attention to this point, here the source code is directly simplified to
class
Writing method (the same as below) - This component is primarily for passing
history
Method, which is the key method for the entire routing implementation
//
class BrowserRouter extends React.Componet {
constructor(props) {
super(a); his.props = props;this.history = createBrowserHistory(_this.props);
}
render() {
return React.createElement(Router, {
history: this.history,
children: this.props.children }); }}Copy the code
createBrowserHistory
The important thing to notice is the createBrowserHistory function, which encapsulates the history method
function createBrowserHistory(props) {
// ...
return {
length: globalHistory.length,
action: 'POP'.location: initialLocation,
createHref: createHref,
push: push,
replace: replace,
go: go,
goBack: goBack,
goForward: goForward,
block: block,
listen: listen
};
}
Copy the code
Just pick a few of the methods that we’re going to use later, and describe them briefly
location
The corresponding value is initialLocation, which, as the name implies, is the initial address, and returns an object containing pathname, hash, and search in the path
location: initialLocation,
Copy the code
transitionManager
setPrompt
/confirmTransitionTo
setPrompt
:prompt
The assignmentconfirmTransitionTo
: according to theprompt
To determine whether to hijack the default callback function and perform a custom onegetUserConfirmation
methods
appendListener
/notifyListeners
: Equivalent to observer mode;- call
appendListener
Add an observer; - call
notifyListeners
Trigger the observer method in turn
- call
var transitionManager = createTransitionManager();
function createTransitionManager() {
var prompt = null;
function setPrompt(nextPrompt) {
prompt = nextPrompt;
return function () {
if (prompt === nextPrompt) prompt = null;
};
}
function confirmTransitionTo(location, action, getUserConfirmation, callback) {
if(prompt ! =null) {
var result = typeof prompt === 'function' ? prompt(location, action) : prompt;
if (typeof result === 'string') {
if (typeof getUserConfirmation === 'function') {
getUserConfirmation(result, callback);
} else {
callback(true); }}else{ callback(result ! = =false); }}else {
callback(true); }}var listeners = [];
function appendListener(fn) {
var isActive = true;
function listener() {
if (isActive) fn.apply(void 0.arguments);
}
listeners.push(listener);
return function () {
isActive = false;
listeners = listeners.filter(function (item) {
returnitem ! == listener; }); }; }function notifyListeners() {
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
listeners.forEach(function (listener) {
return listener.apply(void 0, args);
});
}
return {
setPrompt: setPrompt,
confirmTransitionTo: confirmTransitionTo,
appendListener: appendListener,
notifyListeners: notifyListeners
};
}
Copy the code
Ps. setPrompt is usually only used when the server renders or calls the
setState
- merge
history
,nectState
attribute transitionManager.notifyListeners
Triggers the listener function
function setState(nextState) {
_extends(history, nextState);
history.length = globalHistory.length;
transitionManager.notifyListeners(history.location, history.action);
}
Copy the code
listen
listen
:transitionManager.appendListener
Adding an ObservercheckDOMListeners
: Listen/removepopstate
The eventListen for users to click back, forward, or in the browserjs
In the callhistroy.back()
.history.go()
.history.forward()
Wait, but I can’t hear itpushState
,replaceState
Methods)needsHashChangeListener
: For compatibilityhash
Change does not triggerpopstate
Event browser, so need to add an extra listenerhashchange
Event (again, this case is not considered for the time being)handlePopState
: Equivalent to executionsetState
methods
unlisten
Remove:transitionMagager
In the correspondinglistener
methods
function listen(listener) {
var unlisten = transitionManager.appendListener(listener);
checkDOMListeners(1);
return function () {
checkDOMListeners(-1);
unlisten();
};
}
// checkDomListeners
var listenerCount = 0;
function checkDOMListeners(delta) {
listenerCount += delta;
if (listenerCount === 1 && delta === 1) {
window.addEventListener('popstate', handlePopState);
if (needsHashChangeListener) window.addEventListener('hashChange', handleHashChange);
} else if (listenerCount === 0) {
window.removeEventListener('popstate', handlePopState);
if (needsHashChangeListener) window.removeEventListener('hashChange', handleHashChange); }}function handlePopState(event) {
// Ignore extraneous popstate events in WebKit.
if (isExtraneousPopstateEvent(event)) return;
handlePop(getDOMLocation(event.state));
}
var forceNextPop = false;
function handlePop(location) {
if (forceNextPop) {
forceNextPop = false;
setState();
} else {
var action = 'POP';
transitionManager.confirmTransitionTo(location, action, getUserConfirmation, function (ok) {
if (ok) {
setState({
action: action,
location: location
});
} else {
revertPop(location); // The example process does not go that far, so the forceNextPop state will not change}}); }}Copy the code
push / replace
- corresponding
history.pushState
和history.replaceState
methods - Then according to whether the need to force refresh, to determine the call
window.location.href = href
Force refresh, or callsetState
methods
function push(path, state) {
var action = 'PUSH';
var location = createLocation(path, state, createKey(), history.location);
transitionManager.confirmTransitionTo(location, action, getUserConfirmation, function (ok) {
if(! ok)return;
var href = createHref(location);
var key = location.key,
state = location.state;
if (canUseHistory) { // Determine whether the HISTORY API is supported
globalHistory.pushState({
key: key,
state: state
}, null, href);
if (forceRefresh) {
window.location.href = href;
} else {
var prevIndex = allKeys.indexOf(history.location.key);
var nextKeys = allKeys.slice(0, prevIndex + 1);
nextKeys.push(location.key);
allKeys = nextKeys;
setState({
action: action,
location: location }); }}else {
window.location.href = href; }}); }function replace(path, state) {
var action = 'REPLACE';
var location = createLocation(path, state, createKey(), history.location);
transitionManager.confirmTransitionTo(location, action, getUserConfirmation, function (ok) {
if(! ok)return;
var href = createHref(location);
var key = location.key,
state = location.state;
if (canUseHistory) {
globalHistory.replaceState({
key: key,
state: state
}, null, href);
if (forceRefresh) {
window.location.replace(href);
} else {
var prevIndex = allKeys.indexOf(history.location.key);
if(prevIndex ! = = -1) allKeys[prevIndex] = location.key;
setState({
action: action,
location: location }); }}else {
window.location.replace(href); }}); }Copy the code
Let’s go back to the React Route component. How does the React Route component invoke this interface to implement routing
Router
staticContext
: This is only in usestaticRouter
When it’s called, so it’s calledhistory
In thelisten
methodslisten
: Adds the observer method when listening topopState
Event, the method (update) is triggeredstate
The value of the); When the component fromDOM
, the method is removed- new
context
Object,state.location
,history
Pass it as a parameter
var context = createNamedContext$1("Router");
class Router extends React.Component {
constructor(props) {
super(a);this.props = props;
this.state = {
location: this.props.history.location
}
this._isMounted = false;
this._pendingLocation = null;
if (!this.props.staticContext) {
this.unlisten = props.history.listen(function (location) {
if (this._isMounted) {
this.setState({
location: location
});
} else {
this._pendingLocation = location; }}); }}static computeRootMatch(pathname) {
return {
path: "/".url: "/".params: {},
isExact: pathname === "/"
};
}
componentDidMount() {
this._isMounted = true;
if (this._pendingLocation) {
this.setState({
location: this._pendingLocation }); }};componentWillUnmount() {
if (this.unlisten) this.unlisten();
};
render() {
return React.createElement(context.Provider, {
value: {
history: this.props.history,
location: this.state.location,
match: Router.computeRootMatch(this.state.location.pathname),
staticContext: this.props.staticContext
}
}, React.createElement(historyContext.Provider, {
children: this.props.children || null.value: this.props.history })); }}Copy the code
Link
- According to the
React
Version, useforWardRef
或ref
The callback will beDOM Refs
Expose to parent component - create
<context.Consumer>
Component, passnavigate
Methods (i.e.history
In thepush
或replace
Methods)
var Link = forwardRef(function (_ref2, forwardedRef) {
var _ref2$component = _ref2.component,
component = _ref2$component === void 0 ? LinkAnchor : _ref2$component,
replace = _ref2.replace,
to = _ref2.to,
innerRef = _ref2.innerRef,
rest = _objectWithoutPropertiesLoose(_ref2, ["component"."replace"."to"."innerRef"]);
return React.createElement(__RouterContext.Consumer, null.function (context) {
var history = context.history;
var location = normalizeToLocation(resolveToLocation(to, context.location), context.location); // Concatenate the link address in to
var href = location ? history.createHref(location) : "";
var props = _extends({}, rest, {
href: href,
navigate: function navigate() {
var location = resolveToLocation(to, context.location);
varmethod = replace ? history.replace : history.push; method(location); }});// React 15 compat
if(forwardRefShim ! == forwardRef) { props.ref = forwardedRef || innerRef; }else {
props.innerRef = innerRef;
}
return React.createElement(component, props);
});
});
Copy the code
LinkAnchor
In this example, the Component property is not passed in
, so the default
- create
<a>
The label - add
click
Event, block<a>
Default jump for - If there is no custom
onclick
Method is used by default<Link>
In the componentnavigate
Methods (i.e.history
In thepush
或replace
Methods)
var LinkAnchor = forwardRef(function (_ref, forwardedRef) {
var innerRef = _ref.innerRef,
navigate = _ref.navigate,
_onClick = _ref.onClick,
rest = _objectWithoutPropertiesLoose(_ref, ["innerRef"."navigate"."onClick"]);
var target = rest.target;
var props = _extends({}, rest, {
onClick: function onClick(event) {
try {
if (_onClick) _onClick(event);
} catch (ex) {
event.preventDefault();
throw ex;
}
if(! event.defaultPrevented &&// onClick prevented default
event.button === 0 && ( // ignore everything but left clicks! target || target ==="_self") && // let browser handle "target=_blank" etc.! isModifiedEvent(event)// ignore clicks with modifier keys) { event.preventDefault(); navigate(); }}});// React 15 compat
if(forwardRefShim ! == forwardRef) { props.ref = forwardedRef || innerRef; }else {
props.ref = innerRef;
}
/* eslint-disable-next-line jsx-a11y/anchor-has-content */
return React.createElement("a", props);
});
Copy the code
Switch
React.Children.forEach
: Is equivalent to traversal<Switch>
All of the children of<Route>
, and take out the one that matches the route<Route>
To clone the component for display- If the
<Switch>
Componentlocation
Variables, he will ignorecontext
From up herelocation
Only matches will be rendered<Route>
class Switch extends React.Component {
constructor(props) {
super(a);this.props = props;
}
render() {
return React.createElement(context.Consumer, null.function (context) {
var location = this.props.location || context.location;
var element, match;
React.Children.forEach(this.props.children, function (child) {
if (match == null && React.isValidElement(child)) {
element = child;
var path = child.props.path || child.props.from;
match = path ? matchPath(location.pathname, _extends({}, child.props, {
path: path })) : context.match; }});return match ? React.cloneElement(element, {
location: location,
computedMatch: match
}) : null; }); }}Copy the code
Route
Display the corresponding routing node
- If it is not
<Switch>
If the component is packaged, routes must be matched within the component to determine whether to display the routes
class Route extends React.Component {
constructor(props) {
this.props = props;
}
render() {
return React.createElement(context.Consumer, null.function (context$1) {
var location = _this.props.location || context$1.location;
var match = _this.props.computedMatch ? _this.props.computedMatch // <Switch> already computed the match for us
: _this.props.path ? matchPath(location.pathname, _this.props) : context$1.match;
var props = _extends({}, context$1, {
location: location,
match: match
});
var _this$props = _this.props,
children = _this$props.children,
component = _this$props.component,
render = _this$props.render; // Preact uses an empty array as children by
// default, so use null if that's the case.
if (Array.isArray(children) && children.length === 0) {
children = null;
}
return React.createElement(context.Provider, {
value: props
}, props.match ? children ? typeof children === "function"? process.env.NODE_ENV ! = ="production" ? evalChildrenDev(children, props, _this.props.path) : children(props) : children : component ? React.createElement(component, props) : render ? render(props) : null : typeof children === "function"? process.env.NODE_ENV ! = ="production" ? evalChildrenDev(children, props, _this.props.path) : children(props) : null);
});
};
}
Copy the code
conclusion
In a nutshell
<BrowserRouter>
Internally it will createcontext
Object that will be the component ofstate.location
As acontext.Provider
的value
Value, indicating the current route; It also turns onpopState
Listen for events when listened topopState
Event, will go to the updatestate.location
The value of the<Link>
The component will render the customcomponent
Component or defaulta
Tag, and bind the corresponding click event,pushState
或replaceState
, will also be updatedstate.location
The value of the<Switch>
As acontext
Consumer components, according tolocation
To filter out those that match<Route>
Component to display whenstate.location
Has changed the value of<Switch>
The consumer component will rerender the matching route<Route>
component