这个库输出的模块有:node
exports.createBrowserHistory = createBrowserHistory;
exports.createHashHistory = createHashHistory;
exports.createMemoryHistory = createMemoryHistory;
exports.createLocation = createLocation;
exports.locationsAreEqual = locationsAreEqual;
exports.parsePath = parsePath;
exports.createPath = createPath;复制代码
这里重点分析 createBrowserHistory createMemoryHistoryreact
首先看createBrowserHistory的返回是:web
var history = {
length: globalHistory.length,
action: 'POP',
location: initialLocation,
createHref: createHref,
push: push,
replace: replace,
go: go,
goBack: goBack,
goForward: goForward,
block: block,
listen: listen
};
return history;复制代码
createBrowserHistory主要是基于h5 的history中的pushstate以及replacestate来变动浏览器地址栏。当浏览器不支持h5时,会用location.href直接赋值的办法直接跳转页面,检测是否支持h5 的函数supportsHistory,以及 needsHashChangeListener 是判断是否支持hashchange检测的,以下代码:npm
/**
* Returns true if browser fires popstate on hash change.
* IE10 and IE11 do not.
*/
function supportsPopStateOnHashChange() {
return window.navigator.userAgent.indexOf('Trident') === -1;
}
var needsHashChangeListener = !supportsPopStateOnHashChange();复制代码
createBrowserHistory 的History其实使用的就是window.history的api,会利用window.addEventListener监听popstate和hashchange事件,而且会将listener的回调函数加到队列中,react-router的回调函数是:api
function (location) {
if (_this._isMounted) {
_this.setState({
location: location
});
} else {
_this._pendingLocation = location;
}
}复制代码
其中location就是逻辑图最后listener.apply的参数args,即history 主要是维护一个history栈,监听浏览器变化,控制history的栈记录,而且返回当前location信息给react-router,react-router会根据相应的location render出对应的components。数组
function checkDOMListeners(delta) {
listenerCount += delta;
if (listenerCount === 1 && delta === 1) {
window.addEventListener(PopStateEvent, handlePopState);
if (needsHashChangeListener) window.addEventListener(HashChangeEvent, handleHashChange);
} else if (listenerCount === 0) {
window.removeEventListener(PopStateEvent, handlePopState);
if (needsHashChangeListener) window.removeEventListener(HashChangeEvent, handleHashChange);
}
}复制代码
如上,listenerCount是现有的监听事件函数,checkDOMListeners只在listen函数以及block函数中被调用,参数只为1 与 -1.因此以此来控制add监听或remove监听。浏览器
handlePop函数是在popstate事件触发时的回调函数,主要就是setState action以及location。而setState方法首先利用_extends方法把action和history覆盖到history同属性值上,也就值替换掉当前的state和location。缓存
其次调用notifyListeners方法,这个方法就是调用在listeners队列中的listener.apply()回调方法也就是上面提到的react-router中的listen方法里的函数,而history里的location会传给react-router里的listen回调函数来使用。bash
react-router拿到了当前应该展示那个location页面组件,接下来就是react-router的舞台了。react-router
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);
}
});
}
}复制代码
//setState代码
function setState(nextState) {
_extends(history, nextState);
history.length = globalHistory.length;
transitionManager.notifyListeners(history.location, history.action);
}复制代码
//notifyListeners代码
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);
});
}复制代码
以上方法其实都是调用的window.history的原生api,以下
function push(path, state) {
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.pushState({
key: key,
state: state
}, null, href);
if (forceRefresh) {
window.location.href = href;
} else {
//重点代码:
/**************************/
// 更新存储的allKeys
// allKeys 缓存历史堆栈中的数据标识
// 当location处于history队尾时,实际为push
// 当location处于history中间时,会删除以后的keys,并添加新key ??????????TODO:::::::::
var prevIndex = allKeys.indexOf(history.location.key);
var nextKeys = allKeys.slice(0, prevIndex === -1 ? 0 : prevIndex + 1);
nextKeys.push(location.key);
allKeys = nextKeys;
setState({
action: action,
location: location
});
/*****************************/
}
} else {
warning(state === undefined, 'Browser history cannot push state in browsers that do not support HTML5 history');
window.location.href = href;
}
});
}复制代码
//replace方法点睛之笔:
var prevIndex = allKeys.indexOf(history.location.key);
if (prevIndex !== -1) allKeys[prevIndex] = location.key;
setState({
action: action,
location: location
});复制代码
function go(n) {
globalHistory.go(n);
}
function goBack() {
go(-1);
}
function goForward() {
go(1);
}复制代码
createMemoryHistory的逻辑同createBrowserHistory,可是不是直接使用的window.history的api。
createMemoryHistory 用于在内存中建立彻底虚拟的历史堆栈,只缓存历史记录,但与真实的地址栏无关(不会引发地址栏变动,不会和原生的 history 对象保持同步),也与 popstate, hashchange 事件无关。
createMemoryHistory 的参数 props 接受 getUserConfirmation, initialEntries, initialIndex, keyLength 属性。其中,props.initialEntries 指定最初的历史堆栈内容 history.entries;props.initialIndex 指定最初的索引值 history.index。push, replace 方法均将改变 history.entries 历史堆栈内容;go, goBack, goForward 均基于 history.entries 历史堆栈内容,以改变 history.index 及 history.location。实现参见源码。
这个库输出的模块有:
exports.MemoryRouter = MemoryRouter;
exports.Prompt = Prompt;
exports.Redirect = Redirect;
exports.Route = Route;
exports.Router = Router;
exports.StaticRouter = StaticRouter;
exports.Switch = Switch;
exports.generatePath = generatePath;
exports.matchPath = matchPath;
exports.withRouter = withRouter;
exports.__RouterContext = context;复制代码
重点解析route router
Router里会传入history,history通常是利用history npm包实例化的实例。
var context =
/*#__PURE__*/
createNamedContext("Router");//(1)
var Router =
function (_React$Componet) {
_inheritsLoose(Router,_React$Component);//(2)
Router.computeRootMatch = function computeRootMatch(pathname) { //(3)
return {
path: "/",
url: "/",
params: {},
isExact: pathname === "/"
};
};
function Router(props) {
、、、
、、、
_this._isMounted = false;
_this._pendingLocation = null;
if (!props.staticContext) {
_this.unlisten = props.history.listen(function (location) { //(4) 这里location是props的location
if (_this._isMounted) {
_this.setState({
location: location
});
} else {
_this._pendingLocation = location;
}
});
}
return _this;
}
var _proto = Router.prototype;
_proto.componentDidMount = function componentDidMount() {//(6)
this._isMounted = true;
if (this._pendingLocation) {
this.setState({
location: this._pendingLocation
});
}
};
_proto.componentWillUnmount = function componentWillUnmount() {//(7)
if (this.unlisten) this.unlisten();
};
_proto.render = function render() { //(5)
return React.createElement(context.Provider, {
children: this.props.children || null,
value: {
history: this.props.history,
location: this.state.location,
match: Router.computeRootMatch(this.state.location.pathname),
staticContext: this.props.staticContext
}
});
};
return Router;
}(React.Component);
{
Router.propTypes = {
children: PropTypes.node,
history: PropTypes.object.isRequired,
staticContext: PropTypes.object
};
Router.prototype.componentDidUpdate = function (prevProps) {
warning(prevProps.history === this.props.history, "You cannot change <Router history>");
};
}复制代码
(1)createNamedContext会调用createContext方法,createContext是require的mini-create-react-context,代码大致以下:
//provider
var Provider = {
function Provider() {
var _this;
_this = _Component.apply(this, arguments) || this;
_this.emitter = createEventEmitter(_this.props.value);//createEventEmitter定义了handler=[],实现了on(handler)、off(handler)、get(handler)、set(handler)方法来不一样的将函数放到handler数组后删除;
return _this;
}
var _proto = Provider.prototype;
_proto.getChildContext = function getChildContext() { //getChildContext方法:向react context中绑定全局变量。
var _ref;
return _ref = {}, _ref[contextProp] = this.emitter, _ref;
};
_proto.componentWillReceiveProps = function componentWillReceiveProps(nextProps) {
if (this.props.value !== nextProps.value) {
var oldValue = this.props.value;
var newValue = nextProps.value;
、、、
this.emitter.set(nextProps.value, changedBits);//当createContext有第二个参数时才起做用,因此这里暂时留个疑问,没有立刻看懂。TODO::::::::::::::
、、、
}
}
}
//consumer
`````
````复制代码
(2) _inheritsLoose(Router,_React$Component);是Router继承_React$Component,而_React$Component就是Router包裹的组件。
(3)computeRootMatch Router会有一个默认值
(4)Router会有一个history.listen也就是讲history时的listen函数,当前location也就是经过这里传到了react-router的。
可是这里没有很明白一点:在listen这部分有一块注释,意思是当组件mount以前,若是发生了popstate、hashchange事件,那我能够及时的在mount以前就利用_this._pendingLocation = location;那样在componentDidmount的时候就能够用最新的location进行渲染。我是这么理解的,不敢确认必定正确。??????
// This is a bit of a hack. We have to start listening for location
// changes here in the constructor in case there are any <Redirect>s
// on the initial render. If there are, they will replace/push when
// they mount and since cDM fires in children before parents, we may
// get a new location before the <Router> is mounted.复制代码
(5)Router render:
其实就是将children组件render出来,不过react16里加了provider,因此这里会将以前context.provider里的属性对象等也会包裹进来。
(6)componentDidMount 结合上满(4)
(7)componentWillUnmount 组件卸载时,会调用this.unlisten解除Router监听事件。
从app.js 中,发现 Route 使用方式是<Route exact path="/" component={Home}/>
var Route = function(_React$component) {
function Route() {
return _React$Component.apply(this, arguments) || this;
}
var _proto = Route.prototype;
_proto.render = function 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
});
return React.createElement(context.Provider, {
value: props
}, children && !isEmptyChildren(children) ? children : props.match ? component ? React.createElement(component, props) : render ? render(props) : null : null);
});
}
}
}复制代码
从render 方法能够知道,会经过match、location、path来匹配是否和当前location符合,而后决定render到哪一个具体的children组件。
props = {match, location, history, staticContext} 这些属性在组件中会有很大的用途
1 history:zhuanlan.zhihu.com/p/55837818
2 react-router:juejin.im/post/5b8251…
3 react-router4官方文档:reacttraining.com/react-route…