react-router
源码分析
前言
React router
,是声明式的React路由管理库,以react-router
为核心库。本篇将分析react-router
的实现原理。
在此基础上,扩展出react-router-dom
作为Web环境下的路由管理库,
react-router-native
作为native环境下的路由管理库。(虽然react-native
下可能不如react-navigation
好用2333)
在这篇文章中,将分别对react-router
库中各个API逐个分析
<Router>
<Router>
组件是套在路由最外层的组件,主要作用他的作用有两点
- 接收一个
history
参数,提供一个包含history
、history
的location
和根路由match
的React legacy context
- 通过
history
的listen
接口,监听history
的变化,变化时更新Router
组件的state中的match
,在<Router>
被卸载时取消监听
那么,我们开始阅读<Router>
的代码
import warning from "warning";
import invariant from "invariant";
import React from "react";
import PropTypes from "prop-types";
/**
* The public API for putting history on context.
*/
class Router extends React.Component {
static propTypes = {
history: PropTypes.object.isRequired,
children: PropTypes.node
};
// 这里的contextTypes是为了接收<StaticRouter>提供的context
static contextTypes = {
router: PropTypes.object
};
// `childContextTypes`和`getChildContext`提供了`<Router>`子孙组件的context
static childContextTypes = {
router: PropTypes.object.isRequired
};
getChildContext() {
return {
router: {
...this.context.router,
history: this.props.history,
route: {
// 单独把history的location拎出来,
// 因为location和match要作为额外的props传给`<Route>`和`<Switch>`的子组件。
location: this.props.history.location,
match: this.state.match
}
}
};
}
state = {
match: this.computeMatch(this.props.history.location.pathname)
};
computeMatch(pathname) {
return {
path: "/",
url: "/",
params: {},
isExact: pathname === "/"
};
}
componentWillMount() {
const { children, history } = this.props;
invariant(
children == null || React.Children.count(children) === 1,
"A <Router> may have only one child element"
);
// Do this here so we can setState when a <Redirect> changes the
// location in componentWillMount. This happens e.g. when doing
// server rendering using a <StaticRouter>.
// 在SSR的时候<Redirect>会在`componentWillMount`时执行`history.replace`或`history.push`
// 因此需要在`componentWillMount`时就开始监听history的变化
this.unlisten = history.listen(() => {
this.setState({
match: this.computeMatch(history.location.pathname)
});
});
}
componentWillReceiveProps(nextProps) {
// 警告库的使用者不要改变传入·<Router>·的history属性
warning(
this.props.history === nextProps.history,
"You cannot change <Router history>"
);
}
componentWillUnmount() {
// `<Router>`组件卸载时取消监听
this.unlisten();
}
render() {
const { children } = this.props;
// 断言`<Router>`的children数量是1
return children ? React.Children.only(children) : null;
}
}
export default Router;
<Route>
和<Switch>
在<Route>
和<Switch>
中都调用了matchPath
函数来返回match
对象。当然,如果没有match到,它会返回null
matchPath
调用了一个compilePath
的函数。
compilePath通过path-to-regexp
来将path
字符串转化为正则表达式对象,同时将path中的参数元信息存在变量key
中。
compilePath
和matchPath
import pathToRegexp from "path-to-regexp";
// 对以传入的`pattern`和`option`作为key, 缓存`path-to-regexp`返回的结果
// 这么说可能不严谨,准确的说是以`option`的`end`, `strict`,`sensitive`三个字段作为一级cache的key
// `pattern`作为二级cache的key保存Regexp
const patternCache = {};
// 缓存上限。一个猜想可能是在Native环境下如果页面太多会有问题(然而10000个页面的APP或Web项目规模是有多大2333)
const cacheLimit = 10000;
// 当前缓存的Regexp个数
let cacheCount = 0;
const compilePath = (pattern, options) => {
// 上面说的一级key, 以`end`, `strict`, `sensitive`作为key
const cacheKey = `${options.end}${options.strict}${options.sensitive}`;
// 获得对应`cacheKey`所存在的cache,如果cache不存在创建一个这样的二级caches
const cache = patternCache[cacheKey] || (patternCache[cacheKey] = {});
// 如果有cache那么返回这个cache
if (cache[pattern]) return cache[pattern];
const keys = [];
const re = pathToRegexp(pattern, keys, options);
const compiledPattern = { re, keys };
// `pathToRegexp`会返回一个`Regexp`对象同时修改传入`的keys`数组,往这个数组中写入`pattern`中参数的元信息
// 若缓存未满,写入缓存,下一次就不需要通过`pathToRegexp`重新生成Regexp pattern
if (cacheCount < cacheLimit) {
cache[pattern] = compiledPattern;
cacheCount++;
}
return compiledPattern;
};
/**
* Public API for matching a URL pathname to a path pattern.
*/
const matchPath = (pathname, options = {}, parent) => {
// 在`react-router-redux`库中有一处调用传入的options是string,此处做兼容处理
if (typeof options === "string") options = { path: options };
const { path, exact = false, strict = false, sensitive = false } = options;
// <Route>没有path属性时,返回父组件的match
if (path == null) return parent;
// 获取正则表达式和path中的参数信息
const { re, keys } = compilePath(path, { end: exact, strict, sensitive });
const match = re.exec(pathname);
// 没有匹配到返回null
if (!match) return null;
// 第一个匹配到的是url,后面是path中的各个参数
const [url, ...values] = match;
const isExact = pathname === url;
// <Route>的`exact`为true且当前pathname未能精确匹配返回null
if (exact && !isExact) return null;
// 返回`match`对象
return {
path, // the path pattern used to match
url: path === "/" && url === "" ? "/" : url, // the matched portion of the URL
isExact, // whether or not we matched exactly
params: keys.reduce((memo, key, index) => {
// 将path中的参数(如`:id`这样的参数表达式)和pathname中匹配到的对应参数合并到一个params对象中
memo[key.name] = values[index];
return memo;
}, {})
};
};
export default matchPath;
<Route>
组件
<Route>
也许是react-router
中最重要的组件。他最基本的职能就是,当location匹配时,他会渲染出相同的UI
他分别有component
, render
, children
三个属性,通过他们可以让<Route>
渲染一些东西出来。这三个属性对应下列三个场景下渲染内容的策略
- 给
<Route>
传入一个组件作为component
属性。这种情况下适用于传入一个无状态的函数式组件(React SFC)或者一个有状态React组件(用es6class
语法定义或React.createClass创建的组件)不适用在component
中传入一个匿名函数作为组件。因为每次re-render的时候都会创建一个新的组件,之前的组件会被卸载,然后新的组件被挂载上去,即使他们的内容是一样的。 - 给
<Route>
传入一个匿名函数作为render
属性,这样做不会像传入component
那样每次组件更新时,想让路由渲染的那个组件被挂载和卸载。在他的内部会直接调用render函数,这样就不会有上面一条的组件反复挂载卸载的情况。 - 给
<Route>
传入children
,即给<Route>
子组件。 上面两条在路由不匹配时<Route>
会返回null
,第三条会将路由的match
,location
,history
一并作为props传给children
,由children
自己根据match
、location
等信息决定渲染什么。比如在导航的Tab中渲染各个Tab item,给当前选中的Tab不同样式等情景
import warning from "warning";
import invariant from "invariant";
import React from "react";
import PropTypes from "prop-types";
import matchPath from "./matchPath";
// 用于判断`<Route>`有没有`children`
const isEmptyChildren = children => React.Children.count(children) === 0;
/**
* The public API for matching a single path and rendering.
*/
class Route extends React.Component {
static propTypes = {
// 如果这个`<Route>`的父组件是`<Switch>`,
// 那么他会被传入`computedMatch`。
// 然后`<Route>`的match会直接使用`computedRoute`
computedMatch: PropTypes.object, // private, from <Switch>
path: PropTypes.string,
exact: PropTypes.bool,
strict: PropTypes.bool,
sensitive: PropTypes.bool,
component: PropTypes.func,
render: PropTypes.func,
children: PropTypes.oneOfType([PropTypes.func, PropTypes.node]),
location: PropTypes.object
};
// 接收context
static contextTypes = {
router: PropTypes.shape({
history: PropTypes.object.isRequired,
route: PropTypes.object.isRequired,
staticContext: PropTypes.object
})
};
// 提供一个新的context
static childContextTypes = {
router: PropTypes.object.isRequired
};
getChildContext() {
return {
router: {
...this.context.router,
route: {
// 默认`<Route>`会通过`history`的`location`来判断路由是否匹配上
// 但也可以显式地给`<Route>`传入`location`的props,
// 来使得在当前`history`的`location`不匹配时也能匹配上这个路由
location: this.props.location || this.context.router.route.location,
// 因为`<Route>`子孙的需要接收`<Route>`的`match`,
// 而不是当前`<Route>`的父`<Route>`或`<Router>`的`match`
match: this.state.match
}
}
};
}
state = {
match: this.computeMatch(this.props, this.context.router)
};
computeMatch(
{ computedMatch, location, path, strict, exact, sensitive },
router
) {
// 如果有由`<Switch>`传入的`computedMatch`参数,则直接返回`computedMatch`
if (computedMatch) return computedMatch; // <Switch> already computed the match for us
// 断言`<Route>`被`<Router>`包裹
invariant(
router,
"You should not use <Route> or withRouter() outside a <Router>"
);
const { route } = router;
// 从`props`的`location`或者`context.router.location`中取得`pathname`
const pathname = (location || route.location).pathname;
// 用上个section分析过的`matchPath`函数来获得`match`对象,如果未匹配会返回`null`
return matchPath(pathname, { path, strict, exact, sensitive }, route.match);
}
componentWillMount() {
// 警告用户`component`、`render`、`children`只要传入一个给`<Route>`就够了
warning(
!(this.props.component && this.props.render),
"You should not use <Route component> and <Route render> in the same route; <Route render> will be ignored"
);
warning(
!(
this.props.component &&
this.props.children &&
!isEmptyChildren(this.props.children)
),
"You should not use <Route component> and <Route children> in the same route; <Route children> will be ignored"
);
warning(
!(
this.props.render &&
this.props.children &&
!isEmptyChildren(this.props.children)
),
"You should not use <Route render> and <Route children> in the same route; <Route children> will be ignored"
);
}
componentWillReceiveProps(nextProps, nextContext) {
// 警告用户不要切换<Route>的受控和非受控状态
warning(
!(nextProps.location && !this.props.location),
'<Route> elements should not change from uncontrolled to controlled (or vice versa). You initially used no "location" prop and then provided one on a subsequent render.'
);
warning(
!(!nextProps.location && this.props.location),
'<Route> elements should not change from controlled to uncontrolled (or vice versa). You provided a "location" prop initially but omitted it on a subsequent render.'
);
// 组件props改变时更新`<Route>`的匹配状态
this.setState({
match: this.computeMatch(nextProps, nextContext.router)
});
}
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;
// 额外需要传给`component`、`render`、`children`的props
const props = { match, location, history, staticContext };
if (component) return match ? React.createElement(component, props) : null;
if (render) return match ? render(props) : null;
// 当`children`是一个function(即无状态的函数式组件(React SFC)用或es6 `class`定义的有状态组件)时将上面的`props`传给`children`
if (typeof children === "function") return children(props);
// 否则当children非空时返回这个children。
// 这种情况,`children`是类似`div`这种html原生的标签
// 下面的断言排除了有多个`children`的情况
if (children && !isEmptyChildren(children))
return React.Children.only(children);
return null;
}
}
export default Route;
<Switch>
组件
<Switch>
组件和<Route>
组件在许多地方代码都差不多,所以关于他的分析主要会在render
函数上
与<Route>
一样,也可以显式地给他传入一个location
对象使得当前history.location不匹配时,也使得<Switch>
匹配
import React from "react";
import PropTypes from "prop-types";
import warning from "warning";
import invariant from "invariant";
import matchPath from "./matchPath";
/**
* The public API for rendering the first <Route> that matches.
*/
class Switch extends React.Component {
// contextTypes,用于接收context
static contextTypes = {
router: PropTypes.shape({
route: PropTypes.object.isRequired
}).isRequired
};
static propTypes = {
children: PropTypes.node,
location: PropTypes.object
};
// 断言`<Switch>`被`<Router>`包裹
componentWillMount() {
invariant(
this.context.router,
"You should not use <Switch> outside a <Router>"
);
}
componentWillReceiveProps(nextProps) {
// 警告库使用者不要改变`<Switch>`的受控状态
warning(
!(nextProps.location && !this.props.location),
'<Switch> elements should not change from uncontrolled to controlled (or vice versa). You initially used no "location" prop and then provided one on a subsequent render.'
);
warning(
!(!nextProps.location && this.props.location),
'<Switch> elements should not change from controlled to uncontrolled (or vice versa). You provided a "location" prop initially but omitted it on a subsequent render.'
);
}
render() {
const { route } = this.context.router;
const { children } = this.props;
// 同`<Route>`可以显式传入一个`location`的props,
// 使得在`context.router.history.location`不匹配的情况下,
// 使得`<Switch>`匹配上其中的某个`<Router>`
const location = this.props.location || route.location;
let match, child;
React.Children.forEach(children, element => {
// 没有匹配到且这个子组件是合法的React组件的情况下
if (match == null && React.isValidElement(element)) {
const {
path: pathProp,
exact,
strict,
sensitive,
// `from`是`<Redirect>`组件的props, 是一个能被`path-to-regexp`解析的字符串
// 仅有当前页面的路由匹配从`from`转换的正则表达式时, `<Redirect>`才会重定向url
from
} = element.props;
// 对`<Route>`、·<Switch>、`<Redirect>`的”路径“做兼容处理
const path = pathProp || from;
// 保存当前子组件match和对当前子组件的引用
child = element;
match = matchPath(
location.pathname,
{ path, exact, strict, sensitive },
route.match
);
}
});
return match
// `<Route>`组件会直接使用`computedMatch`作为它state中的`match`
? React.cloneElement(child, { location, computedMatch: match })
: null;
}
}
export default Switch
<Redirect>
<Redirect>
适用于重定向。若在服务端渲染且使用<StaticRouter>
,会在componentWillMount
第一次改变history
的location
,其它情况下会在componentDidMount
改变history
的location
。
传入<Redirect>
的to
是一个能被path-to-regexp
解析的字符串,他的参数会被from
属性中的匹配到的参数填入。
当传入<Redirect>
的to
属性改变时,也会改变history
的location
,这种情况只会在客户端渲染时发生
它还有一个from
的属性,是一个path-to-regexp
能解析的字符串,这个属性仅当<Redirect>
组件的父组件是<Switch>
时才有效。
若form
存在,当且仅当当前的pathname
能匹配由form
属性转化出的正则表达式,该重定向才会生效
<Redirect>组件
import React from "react";
import PropTypes from "prop-types";
import warning from "warning";
import invariant from "invariant";
import { createLocation, locationsAreEqual } from "history";
import generatePath from "./generatePath";
/**
* The public API for updating the location programmatically
* with a component.
*/
class Redirect extends React.Component {
static propTypes = {
// 由`<Switch>`传入的`computedMatch`属性
computedMatch: PropTypes.object, // private, from <Switch>
push: PropTypes.bool,
from: PropTypes.string,
to: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired
};
static defaultProps = {
push: false
};
static contextTypes = {
router: PropTypes.shape({
history: PropTypes.shape({
push: PropTypes.func.isRequired,
replace: PropTypes.func.isRequired
}).isRequired,
staticContext: PropTypes.object
}).isRequired
};
// 判断是否是`<StaticRouter>`
isStatic() {
return this.context.router && this.context.router.staticContext;
}
componentWillMount() {
invariant(
this.context.router,
"You should not use <Redirect> outside a <Router>"
);
// 有`<StaticRouter>`说明是服务端渲染或测试。没有`componentDidMount`生命周期。
// 因此在`componentWillMount`中改变`history`
if (this.isStatic()) this.perform();
}
componentDidMount() {
// 其它情况,在componentDidMount中改变`history`
if (!this.isStatic()) this.perform();
}
componentDidUpdate(prevProps) {
const prevTo = createLocation(prevProps.to);
const nextTo = createLocation(this.props.to);
if (locationsAreEqual(prevTo, nextTo)) {
warning(
false,
`You tried to redirect to the same route you're currently on: ` +
`"${nextTo.pathname}${nextTo.search}"`
);
return;
}
// 组件更新时执行重定向
this.perform();
}
// 计算重定向的目标url
computeTo({ computedMatch, to }) {
// 当`<Switch>`匹配到`<Redirect>`的`from`属性时,`computedMatch`会作为属性传入
// 没有匹配到`from`时,`<Redirect>`根本就不会渲染出来
// computedMath.params中便是`from`的path中匹配到的参数
// 通过`generatePath`函数可以将`path`和`params`组装成最终重定向的路径
if (computedMatch) {
if (typeof to === "string") {
return generatePath(to, computedMatch.params);
} else {
return {
...to,
pathname: generatePath(to.pathname, computedMatch.params)
};
}
}
return to;
}
perform() {
const { history } = this.context.router;
const { push } = this.props;
const to = this.computeTo(this.props);
// 由传入`<Redirect>`的`push`属性来决定调用`history.push`或`history.replace`
if (push) {
history.push(to);
} else {
history.replace(to);
}
}
render() {
return null;
}
}
export default Redirect;
generatePath
函数
顺便看一下这个函数的实现思路与matchPath
类似。generatePath
会缓存PathToRegexp.compile
的结果
import pathToRegexp from "path-to-regexp";
const patternCache = {};
const cacheLimit = 10000;
let cacheCount = 0;
const compileGenerator = pattern => {
// 一级cache的key是pattern
const cacheKey = pattern;
const cache = patternCache[cacheKey] || (patternCache[cacheKey] = {});
if (cache[pattern]) return cache[pattern];
// 通过`pathToRegexp.compile`来获得能生成路径的函数
const compiledGenerator = pathToRegexp.compile(pattern);
// 缓存这个函数(为什么还需要`pattern`作为二级缓存的key呢)
if (cacheCount < cacheLimit) {
cache[pattern] = compiledGenerator;
cacheCount++;
}
return compiledGenerator;
};
/**
* Public API for generating a URL pathname from a pattern and parameters.
*/
const generatePath = (pattern = "/", params = {}) => {
// 没有pattern传入或者传入的pattern为"/"(等效于没有传入),不匹配,直接返回"/"作为重定向的url
if (pattern === "/") {
return pattern;
}
// 获得能从"path"到url的参数,并通过他生成url返回之
const generator = compileGenerator(pattern);
return generator(params, { pretty: true });
};
export default generatePath;
<StaticRouter>
<StaticRouter>
封装了<Router>
组件,正如他名字中的static
,它的history
的location
永远不会改变。在SSR(服务端渲染)和测试时,会变得非常有用
import warning from "warning";
import invariant from "invariant";
import React from "react";
import PropTypes from "prop-types";
import { createLocation, createPath } from "history";
import Router from "./Router";
// 用于在url开头加上"/"
const addLeadingSlash = path => {
return path.charAt(0) === "/" ? path : "/" + path;
};
// 用于在`location`的`pathname`前加上`basename`
const addBasename = (basename, location) => {
if (!basename) return location;
return {
...location,
pathname: addLeadingSlash(basename) + location.pathname
};
};
// 返回移除了location的pathname前basename的的location
const stripBasename = (basename, location) => {
if (!basename) return location;
const base = addLeadingSlash(basename);
if (location.pathname.indexOf(base) !== 0) return location;
return {
...location,
pathname: location.pathname.substr(base.length)
};
};
// 将`location`转化为url
const createURL = location =>
typeof location === "string" ? location : createPath(location);
// 虚设的staticHander,用于在`history.go`, `history.goBack`, `history.goForward`调用时提醒库的使用者这是一个静态的路由,不能这么做
const staticHandler = methodName => () => {
invariant(false, "You cannot %s with <StaticRouter>", methodName);
};
// 什么都不坐的空函数
const noop = () => {};
/**
* The public top-level API for a "static" <Router>, so-called because it
* can't actually change the current location. Instead, it just records
* location changes in a context object. Useful mainly in testing and
* server-rendering scenarios.
*/
class StaticRouter extends React.Component {
static propTypes = {
basename: PropTypes.string,
context: PropTypes.object.isRequired,
location: PropTypes.oneOfType([PropTypes.string, PropTypes.object])
};
static defaultProps = {
basename: "",
location: "/"
};
static childContextTypes = {
router: PropTypes.object.isRequired
};
// 他的子孙组件可以通过context接收staticContext来得知路由是否是静态的
getChildContext() {
return {
router: {
staticContext: this.props.context
}
};
}
// 调用`createHref`以获得以`baseName`开头的URL
createHref = path => addLeadingSlash(this.props.basename + createURL(path));
// 在调用`history.push`时,将这个动作相关的信息记录在传入`<StaticRouter>`的context属性中
// 那么外部就可以通过context来进行测试
handlePush = location => {
const { basename, context } = this.props;
context.action = "PUSH";
context.location = addBasename(basename, createLocation(location));
context.url = createURL(context.location);
};
// 同上,写入context来记录路由的replace
handleReplace = location => {
const { basename, context } = this.props;
context.action = "REPLACE";
context.location = addBasename(basename, createLocation(location));
context.url = createURL(context.location);
};
// 调用用history.listen什么事情也不会发生。返回的`unlisten`函数也应该是个空函数
handleListen = () => noop;
// 静态页面不需要在页面离开时发出提示。因为根本没有这样的场景
handleBlock = () => noop;
componentWillMount() {
warning(
!this.props.history,
"<StaticRouter> ignores the history prop. To use a custom history, " +
"use `import { Router }` instead of `import { StaticRouter as Router }`."
);
}
render() {
const { basename, context, location, ...props } = this.props;
// 一个静态的history对象
// `<Redirect>`的重定向会被记录到context中
// 正如上文分析,会在使用静态路由时,`<Redirect>`会在`componentWillMount`中
// 调用`history.push`或`history.replace`,这两个行为就被记录在了`context`中
const history = {
createHref: this.createHref,
action: "POP",
location: stripBasename(basename, createLocation(location)),
push: this.handlePush,
replace: this.handleReplace,
go: staticHandler("go"),
goBack: staticHandler("goBack"),
goForward: staticHandler("goForward"),
listen: this.handleListen,
block: this.handleBlock
};
return <Router {...props} history={history} />;
}
}
export default StaticRouter;
有时我们需要在用户离开页面时提示用户。如果我们需要阻止用户离开当前页面时,可以使用<Prompt>
组件
<Prompt>
组件通过history.block
来实现这样的功能
<Prompt>
import React from "react";
import PropTypes from "prop-types";
import invariant from "invariant";
/**
* The public API for prompting the user before navigating away
* from a screen with a component.
*/
class Prompt extends React.Component {
static propTypes = {
// 当`when`为`true`时,`<Prompt>`才会生效
when: PropTypes.bool,
// 需要提示的信息,可以是一个字符串,或者一个形如`(history: History, action: string) => string`这样的函数
message: PropTypes.oneOfType([PropTypes.func, PropTypes.string]).isRequired
};
static defaultProps = {
when: true
};
// 接收context
static contextTypes = {
router: PropTypes.shape({
history: PropTypes.shape({
block: PropTypes.func.isRequired
}).isRequired
}).isRequired
};
// 取消之前添加到`history`的`block`
// 调用`history.block`,当用户离开时给予提示。将`history.block`返回的用于unblock的函数绑定到`this.unblock`上
enable(message) {
if (this.unblock) this.unblock();
this.unblock = this.context.router.history.block(message);
}
// 取消添加到`history`的`block`
disable() {
if (this.unblock) {
this.unblock();
this.unblock = null;
}
}
componentWillMount() {
invariant(
this.context.router,
"You should not use <Prompt> outside a <Router>"
);
// 如果`when`为`true`时,让用户再离开页面时收到提示
if (this.props.when) this.enable(this.props.message);
}
componentWillReceiveProps(nextProps) {
if (nextProps.when) {
// 当之前传入的`when`为`false`或之前传入的`message`和即传入的`message`不同时
// 重新设置用户离开页面时的提示
if (!this.props.when || this.props.message !== nextProps.message)
this.enable(nextProps.message);
} else {
this.disable();
}
}
componentWillUnmount() {
// 组件卸载后取消离开时的提示
this.disable();
}
render() {
return null;
}
}
export default Prompt;
<MemoryRouter>
<MemoryRouter>
适用于无DOM的情景下,比如React-Native
和单元测试
他通过history.createMemoryHistory
来创建history
import warning from "warning";
import React from "react";
import PropTypes from "prop-types";
import { createMemoryHistory as createHistory } from "history";
import Router from "./Router";
/**
* The public API for a <Router> that stores location in memory.
*/
class MemoryRouter extends React.Component {
static propTypes = {
// 初始的history stack
initialEntries: PropTypes.array,
// 初始history stack的index, 页面初始的location由它来决定
initialIndex: PropTypes.number,
// 当用户离开当前页面时的弹窗。
// 在没有DOM的情况下不能使用`window.confirm`来弹窗,因此需要库的使用者自己去定义这种行为
getUserConfirmation: PropTypes.func,
// `location.key`的长度
keyLength: PropTypes.number,
children: PropTypes.node
};
history = createHistory(this.props);// 用`props`中的`initialEntries`, `initialIndex`, `getUserConfirmation`, `keyLength`来创建`memoryHistory`
componentWillMount() {
warning(
!this.props.history,
"<MemoryRouter> ignores the history prop. To use a custom history, " +
"use `import { Router }` instead of `import { MemoryRouter as Router }`."
);
}
render() {
return <Router history={this.history} children={this.props.children} />;
}
}
export default MemoryRouter;