使用
version: 5.2.0
官网的使用示例如下:
import React from "react";
import {
BrowserRouter,
Switch,
Route
} from "react-router-dom";
export default function root() {
return (
<BrowserRouter>
<Switch>
<Route exact path="/">
<Home />
</Route>
<Route path="/about">
<About />
</Route>
<Route path="/dashboard">
<Dashboard />
</Route>
</Switch>
</BrowserRouter>
);
}
function Home() {
return (
<div>
<Link to="/">Home</Link>
</div>
);
}
以使用最多的BrowserRouter来分析。
源码
把下载的源码打开可以发现在packages下有4个目录:
-packages
--react-router
--react-router-config
--react-router-dom
--react-router-native
React-Router-Dom
//packages/react-router-dom/modules/BrowserRouter.js
import { Router } from "react-router";
import { createBrowserHistory as createHistory } from "history";
class BrowserRouter extends React.Component {
//这里使用createBrowserHistory创建了一个history对象
history = createHistory(this.props);
render() {
return <Router history={this.history} children={this.props.children} />;
}
}
History包
//node_modules/history/cjs/history.js
function createBrowserHistory(props) {
...
//省略其他代码
var globalHistory = window.history;
...
//方法定义
function getDOMLocation(historyState) {...}
function setState(nextState) {...}
....
//我们挑一个最常用的push方法来看
function push(path, state) {
var action = 'PUSH';
transitionManager.confirmTransitionTo(location, action, getUserConfirmation, function (ok) {
...
if (canUseHistory) {
globalHistory.pushState({
key: key,
state: state
}, null, href);
if (forceRefresh) {
window.location.href = href;
} else {
...
setState({
action: action,
location: location
});
}
} else {
window.location.href = href;
}
});
}
//再看看listen
function listen(listener) {
var unlisten = transitionManager.appendListener(listener);
checkDOMListeners(1);
return function () {
checkDOMListeners(-1);
unlisten();
};
}
function checkDOMListeners(delta) {
listenerCount += delta;
if (listenerCount === 1 && delta === 1) {
//监听popstate事件
window.addEventListener(PopStateEvent, handlePopState);
if (needsHashChangeListener) window.addEventListener(HashChangeEvent, handleHashChange);
} else if (listenerCount === 0) {
window.removeEventListener(PopStateEvent, handlePopState);
if (needsHashChangeListener) window.removeEventListener(HashChangeEvent, handleHashChange);
}
}
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;
}
看完上面的代码核心的东西就出来了:history包对window.history进行一层包装,使用pushState和监听popState事件等方式,提供给上层应用进行处理。
React-Router
//packages/react-router/modules/Router.js
import HistoryContext from "./HistoryContext.js";
import RouterContext from "./RouterContext.js";
class Router extends React.Component {
constructor(props) {
super(props);
this.state = {
location: props.history.location
};
this._isMounted = false;
this._pendingLocation = null;
if (!props.staticContext) {
this.unlisten = props.history.listen(location => {
if (this._isMounted) {
this.setState({ location });
} else {
this._pendingLocation = location;
}
});
}
}
componentDidMount() {
this._isMounted = true;
if (this._pendingLocation) {
this.setState({ location: this._pendingLocation });
}
}
componentWillUnmount() {
if (this.unlisten) {
this.unlisten();
this._isMounted = false;
this._pendingLocation = null;
}
}
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>
);
}
}
添加history.listen事件监听,通过Router类组件的setState来处理React的重新渲染。
//RouterContext和HistoryContext分别如下
const context = /*#__PURE__*/ createNamedContext("Router");
const historyContext = /*#__PURE__*/ createNamedContext("Router-History");
//
import createContext from "mini-create-react-context";
const createNamedContext = name => {
const context = createContext();
context.displayName = name;
return context;
};
这里有2个provider,一个是叫Router的React Context,另外一个是叫Router-History的React Context。
所以Router就是一个包装过的React Context的Provider。
Route
再来看看Route,这个应该是Context的Consumer
//packages/react-router/modules/Route.js
import RouterContext from "./RouterContext.js";
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
: this.props.path
? matchPath(location.pathname, this.props)
: context.match;
const props = { ...context, location, match };
let { children, component, render } = this.props;
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>
);
}
}
总结
React-Router使用React Context作为数据中心在整个应用中传递包装过的history对象,并监听浏览器的popstate通过setState(Router.js)改变location来触发重新渲染。