react router 基于 history 库实现,history 有统一的 api 兼容不同浏览器、不同环境下的历史记录管理,history 包含三种模式:
对应三大方法的实现:
createHashHistory
url 前进
window.location.replace(window.location.pathname + window.location.search + '#' + path);
url 后退
createBrowserHistory
url 前进
url 后退
createMemoryHistory
switch (location.action) {
case 'PUSH':
entries.push(location);
break;
case 'REPLACE':
entries[current] = location;
break;
}
current += n
entries.pop()
function createKey() {
return Math.random()
.toString(36)
.substr(2, 8);
}
react-router 的核心组件包括 Route 、MemoryRouter (不涉及 dom 在 react-router 中定义),Router、Link、BrowserRouter、HistoryRouter(涉及到 dom 在 react-dom 中定义)
还有两个 context 组件 RouterContext 、HistoryContext 用于在组件中传递 router 和 history 相关的 props。
还有个 createNameContext 用于创建有displayName的context
react-
router 基本原理如下:
Route、Router、Link 三个组件即可构成一个基本 react-router应用
使用 route 时,在 route 之外会用 browserRouter HistoryRouter 包起来,他们是基于 router 组件实现的。
BrowserRouter、HistoryRouter、MemoryRouter 本质都是调用 Router 组件。
router 组件 调用 history 的 listen 监听 url 变化, 比较 url 是否 rootmatch 从而更新下级组件的 match 状态 props:
class Router extends React.Component {
static computeRootMatch(pathname) {
return { path: "/", url: "/", params: {}, isExact: pathname === "/" };
}
...
if (!props.staticContext) {
this.unlisten = props.history.listen(location => {
if (this._isMounted) {
this.setState({ location });
} else {
this._pendingLocation = location;
}
});
}
}
render() {
return (
<RouterContext.Provider
value={{
history: this.props.history,
location: this.state.location,
match: Router.computeRootMatch(this.state.location.pathname),
staticContext: this.props.staticContext
}}
>
<HistoryContext.Provider
children={this.props.children || null}
value={this.props.history}
/>
</RouterContext.Provider>
);
}
}
类似 a 标签,用于主动触发改变 url 的方法 。从 context 中拿到 history ,再根据 Props 有没有 replace 去执行 history.replace 和 history.push
const Link = forwardRef(
(
{
component = LinkAnchor,
replace,
to,
innerRef, // TODO: deprecate
...rest
},
forwardedRef
) => {
return (
<RouterContext.Consumer>
{context => {
const { history } = context;
const location = normalizeToLocation(
resolveToLocation(to, context.location),
context.location
);
const href = location ? history.createHref(location) : "";
const props = {
...rest,
href,
navigate() {
const location = resolveToLocation(to, context.location);
const method = replace ? history.replace : history.push;
method(location);
}
};
...
return React.createElement(component, props);
}}
</RouterContext.Consumer>
);
}
);
用于在 url 改变时判断如何更新视图:自身有props path,通过比较 path 和 url 。
比较的时候会做缓存,缓存上限 10000。
匹配成功则渲染 component props 、render props、 children props,排序即优先级:
class Route extends React.Component {
render() {
return (
<RouterContext.Consumer>
{context => {
...
const location = this.props.location || context.location;
const match = this.props.computedMatch
? this.props.computedMatch // <Switch> already computed the match for us
: this.props.path
? matchPath(location.pathname, this.props)
: context.match;
const props = { ...context, location, match };
let { children, component, render } = this.props;
// Preact uses an empty array as children by
// default, so use null if that's the case.
if (Array.isArray(children) && isEmptyChildren(children)) {
children = null;
}
return (
<RouterContext.Provider value={props}>
{props.match
? children
? typeof children === "function"
? __DEV__
? evalChildrenDev(children, props, this.props.path)
: children(props)
: children
: component
? React.createElement(component, props)
: render
? render(props)
: null
: typeof children === "function"
? __DEV__
? evalChildrenDev(children, props, this.props.path)
: children(props)
: null}
</RouterContext.Provider>
);
}}
</RouterContext.Consumer>
);
}
}