BrowserRouter.prototype.componentDidMount = function() { warning( !this.props.history, "<BrowserRouter> ignores the history prop. To use a custom history, " + "use `import { Router }` instead of `import { BrowserRouter as Router }`." ); }; }
// 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. // _isMounted 表示组件是否加载完成 this._isMounted = false; // 组件未加载完毕,但是 location 发生的变化,暂存在 _pendingLocation 字段中 this._pendingLocation = null;
let history: BrowserHistory = { get action() { return action; }, get location() { return location; }, createHref, push, replace, go, back() { go(-1); }, forward() { go(1); }, listen(listener) { return listeners.push(listener); }, block(blocker) { let unblock = blockers.push(blocker);
if (blockers.length === 1) { window.addEventListener(BeforeUnloadEventType, promptBeforeUnload); }
returnfunction() { unblock();
// Remove the beforeunload listener so the document may // still be salvageable in the pagehide event. // See https://html.spec.whatwg.org/#unloading-documents if (!blockers.length) { window.removeEventListener(BeforeUnloadEventType, promptBeforeUnload); } }; } };
functionpush(to: To, state?: State) { // 这里是一个枚举值 let nextAction = Action.Push; let nextLocation = getNextLocation(to, state); // 顾名思义,就是再来一次 functionretry() { push(to, state); }
if (allowTx(nextAction, nextLocation, retry)) { let [historyState, url] = getHistoryStateAndUrl(nextLocation, index + 1);
// TODO: Support forced reloading // try...catch because iOS limits us to 100 pushState calls :/ try { // MDN的地址: https://developer.mozilla.org/zh-CN/docs/Web/API/History/pushState globalHistory.pushState(historyState, '', url); } catch (error) { // They are going to lose state here, but there is no real // way to warn them about it since the page will refresh... // MDN的地址: https://developer.mozilla.org/zh-CN/docs/Web/API/Location/assign window.location.assign(url); }
const PopStateEventType = 'popstate'; let blockedPopTx: Transition | null = null; functionhandlePop() { // 如果有 if (blockedPopTx) { blockers.call(blockedPopTx); blockedPopTx = null; } else { let nextAction = Action.Pop; let [nextIndex, nextLocation] = getIndexAndLocation();
if (blockers.length) { if (nextIndex != null) { let delta = index - nextIndex; if (delta) { // Revert the POP blockedPopTx = { action: nextAction, location: nextLocation, retry() { go(delta * -1); } };
go(delta); } } else { // Trying to POP to a location with no index. We did not create // this location, so we can't effectively block the navigation. warning( false, // TODO: Write up a doc that explains our blocking strategy in // detail and link to it here so people can understand better what // is going on and how to avoid it. `You are trying to block a POP navigation to a location that was not ` + `created by the history library. The block will fail silently in ` + `production, but in general you should do all navigation with the ` + `history library (instead of using window.history.pushState directly) ` + `to avoid this situation.` ); } } else { applyTx(nextAction); } } }
class Switch extends React.Component { render() { return ( <RouterContext.Consumer> {context => { invariant(context, "You should not use <Switch> outside a <Router>");
// We use React.Children.forEach instead of React.Children.toArray().find() // here because toArray adds keys to all child elements and we do not want // to trigger an unmount/remount for two <Route>s that render the same // component at different URLs. // React.Children.forEach 对子元素做遍历 React.Children.forEach(this.props.children, child => { // 只要找到一个 match,那么就不会再进来了 if (match == null && React.isValidElement(child)) { element = child;
/** * The public API for matching a single path and rendering. */ classRouteextendsReact.Component{ render() { return ( <RouterContext.Consumer> {context => { invariant(context, "You should not use <Route> outside a <Router>");
// props 就是更新后的 context // location 做了更新(有可能是用户传入的location) // 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. // children 默认是个空数组,如果是默认情况,置为 null if (Array.isArray(children) && children.length === 0) { children = null; }
return ( // RouterContext 中更新了 location, match <RouterContext.Provider value={props}> {props.match // 首先判断的是有无 children ? children // 如果 children 是个函数,执行,否则直接返回 children ? typeof children === "function" : children(props) : children // 如果没有 children,判断有无 component : component // 有 component,重新新建一个 component ? React.createElement(component, props) // 没有 component,判断有无 render : render // 有 render,执行 render 方法 ? render(props) // 没有返回 null : null
// 这里是不 match 的情况,判断 children 是否函数 : typeof children === "function" // 是的话执行 ? children(props) : null} </RouterContext.Provider> ); }} </RouterContext.Consumer> ); } }
import React from "react"; import PropTypes from "prop-types"; import invariant from "tiny-invariant";
import Lifecycle from "./Lifecycle.js"; import RouterContext from "./RouterContext.js";
/** * The public API for prompting the user before navigating away from a screen. */ function Prompt({ message, when = true }) { return ( <RouterContext.Consumer> {context => { invariant(context, "You should not use <Prompt> outside a <Router>");
/** * The public API for navigating programmatically with a component. */ functionRedirect({ computedMatch, to, push = false }) { return ( // 啥都有的大哥 RouterContext <RouterContext.Consumer> {context => { invariant(context, "You should not use <Redirect> outside a <Router>");