In‑Depth Source Code Analysis of React‑Router (v4/v5) and Its Core Components
This article provides a comprehensive walkthrough of React‑Router's internal architecture, detailing the implementation of key components such as Router, Route, Switch, Redirect, Prompt, Link, and withRouter, while explaining how history, matchPath, and path‑to‑regexp work together to enable client‑side routing in modern React applications.
The author introduces their background and the motivation for studying react‑router, noting the need to understand its inner workings to improve development efficiency.
React 16.3 introduced official Context support, and react‑router began adopting Context APIs from early 2018. The article uses version 4.3.1 of react‑router for illustration.
React‑router v4 is a pure React implementation of routing, built on top of the history library. It provides three types of history implementations—browser, hash, and memory—each exposing methods like push, replace, go, goBack, and goForward.
The project structure of react‑router and react‑router‑dom is compared, showing that many components are re‑exported from the core library.
Router Component
The Router component is the top‑level provider of routing context. It receives a required history prop and sets up a context object containing the current router state. It listens to history changes in componentWillMount (for server‑side rendering compatibility) and updates its internal match state via computeMatch() . The component only renders a single child and uses getChildContext() to pass the router data to descendants.
getChildContext() {
return {
router: {
...this.context.router,
history: this.props.history,
route: {
location: this.props.history.location,
match: this.state.match
}
}
};
}Route Component
The Route component matches the current location against a path using matchPath() . It supports three rendering props— component , render , and children —which are evaluated in that order. When a match occurs, it passes { match, location, history, staticContext } to the rendered element.
render() {
const { match } = this.state;
const { children, component, render } = this.props;
const { history, route, staticContext } = this.context.router;
const location = this.props.location || route.location;
const props = { match, location, history, staticContext };
if (component) return match ? React.createElement(component, props) : null;
if (render) return match ? render(props) : null;
if (typeof children === "function") return children(props);
if (children && !isEmptyChildren(children)) return React.Children.only(children);
return null;
}matchPath Function
matchPath() converts a path pattern into a regular expression using path‑to‑regexp , then tests the current pathname. It returns an object containing path , url , isExact , and extracted params if the match succeeds.
function matchPath(pathname, options = {}, parent) {
const { path, exact = false, strict = false, sensitive = false } = options;
if (!path) return parent;
const { re, keys } = compilePath(path, { end: exact, strict, sensitive });
const match = re.exec(pathname);
if (!match) return null;
const [url, ...values] = match;
const isExact = pathname === url;
if (exact && !isExact) return null;
const params = keys.reduce((memo, key, index) => {
memo[key.name] = values[index];
return memo;
}, {});
return { path, url: path === '/' && url === '' ? '/' : url, isExact, params };
}Switch Component
Switch iterates over its children and renders the first Route that matches the current location, using the same matchPath() logic.
render() {
const { route } = this.context.router;
const { children } = this.props;
const location = this.props.location || route.location;
let match;
React.Children.forEach(children, (element) => {
if (!match && React.isValidElement(element)) {
const { path, exact, strict, sensitive } = element.props;
const child = element;
match = matchPath(location.pathname, { path, exact, strict, sensitive }, route.match);
if (match) return React.cloneElement(child, { location, computedMatch: match });
}
});
return match;
}Redirect Component
The Redirect component performs a navigation by calling history.push or history.replace . It computes the target URL with generatePath() when route parameters are needed, and handles server‑side rendering via isStatic() .
perform() {
const { computedMatch, to } = this.props;
const location = computeTo({ computedMatch, to });
this.context.router.history[replace ? 'replace' : 'push'](location);
}Prompt Component
Prompt uses history.block() to intercept navigation when the when prop is true, displaying a custom message or function.
enable(message) {
if (this.unblock) this.unblock();
this.unblock = this.context.router.history.block(message);
}Link Component
Link renders an <a> element that intercepts clicks, prevents default navigation, and uses the router's history to push or replace the target location.
handleClick = event => {
if (this.props.onClick) this.props.onClick(event);
if (!event.defaultPrevented && event.button === 0 && !this.props.target && !isModifiedEvent(event)) {
event.preventDefault();
const { history } = this.context.router;
const location = typeof to === 'string' ? createLocation(to, null, null, history.location) : to;
const href = history.createHref(location);
return (
);
}
}withRouter Higher‑Order Component
withRouter wraps a component with a Route that injects routing props ( history , location , match ) and forwards refs using hoist‑non‑react‑statics to preserve static properties.
const withRouter = Component => {
const C = props => {
const { wrappedComponentRef, ...remainingProps } = props;
return (
(
)} />
);
};
C.displayName = `withRouter(${Component.displayName || Component.name})`;
C.WrappedComponent = Component;
C.propTypes = { wrappedComponentRef: PropTypes.func };
return hoistStatics(C, Component);
};The article concludes with reference links to the official React Router API documentation and GitHub repository.
Qunar Tech Salon
Qunar Tech Salon is a learning and exchange platform for Qunar engineers and industry peers. We share cutting-edge technology trends and topics, providing a free platform for mid-to-senior technical professionals to exchange and learn.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.