React-router源码解读
我们常用的有React-router的监测路由发生变化的两种方法 BrowserRouter 和 HashRouter ,这两个区别在于一个监测的是url 的hash 变化做出处理,而 一个是通过操作history的会话记录实现
下面分别分析两种方式的实现以期能实现一个简易的路由器
我们先来看下官网提供的示例:
import React from "react";
import {
BrowserRouter as Router,
Switch,
Route,
Link
} from "react-router-dom";
export default function App() {
return (
<Router>
<div>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
<li>
<Link to="/users">Users</Link>
</li>
</ul>
</nav>
{/* A <Switch> looks through its children <Route>s and
renders the first one that matches the current URL. */}
<Switch>
<Route path="/about">
<About />
</Route>
<Route path="/users">
<Users />
</Route>
<Route path="/">
<Home />
</Route>
</Switch>
</div>
</Router>
);
}
function Home() {
return <h2>Home</h2>;
}
function About() {
return <h2>About</h2>;
}
function Users() {
return <h2>Users</h2>;
}
上面代码在浏览器中渲染出来demo如下:

对比看到Router里面的元素成功被渲染出来,且可以随着location的pathname变化而 改变内容,我们继续看react-router-dom.js文件中的对应代码,我们用的是BrowserRouter
BrowserRouter.js 函数中返回一个class组件,下面dev模式下会给出了提示信息等
import React from "react";
import { Router } from "react-router";
import { createBrowserHistory as createHistory } from "history";
import PropTypes from "prop-types";
import warning from "tiny-warning";
/**
* The public API for a <Router> that uses HTML5 history.
*/
class BrowserRouter extends React.Component {
history = createHistory(this.props); // 新建history
render() {
return <Router history={this.history} children={this.props.children} />;
}
}
if (__DEV__) {
BrowserRouter.propTypes = {...};
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 }`."
);
};
}
export default BrowserRouter;
history = createHistory(this.props); ` createHistory 是引用了history包里面的函数,它基于html5 的history API扩展对象,下面精简列出createHistory函数的 内容:
function createBrowserHistory(props) {
if (props === void 0) {
props = {};
}
...
var globalHistory = window.history; // 新建history对象
var canUseHistory = supportsHistory(); // 判断是否支持history用法,部分api需要兼容
...
function go(n) {
globalHistory.go(n);
}
function push(path, state) {
...
transitionManager.confirmTransitionTo(location, action, getUserConfirmation, function (ok) {
...
if (canUseHistory) {
globalHistory.pushState({
key: key,
state: state
}, null, href);
if (forceRefresh) {
window.location.href = href;
} else {
var prevIndex = allKeys.indexOf(history.location.key);
var nextKeys = allKeys.slice(0, prevIndex + 1);
nextKeys.push(location.key);
allKeys = nextKeys;
setState({
action: action,
location: location
});
}
} else {
process.env.NODE_ENV !== "production" ? warning(state === undefined, 'Browser history cannot push state in browsers that do not support HTML5 history') : void 0;
window.location.href = href;
}
});
}
...
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;
}
下面继续看BrowserRouter
render() {
return <Router history={this.history} children={this.props.children} />;
}
Router这个元素对应下面的函数
var Router = function (_React$Component) {
_inheritsLoose(Router, _React$Component);
...
function Router(props) {
var _this;
...
_proto.render = function render() {
return React.createElement(context.Provider, {
value: {
history: this.props.history,
location: this.state.location,
match: Router.computeRootMatch(this.state.location.pathname),
staticContext: this.props.staticContext
}
}, React.createElement(historyContext.Provider, {
children: this.props.children || null,
value: this.props.history
}));
};
return Router;
}(React.Component);
常用组件
Switch
var Switch = function (_React$Component) {
_inheritsLoose(Switch, _React$Component);
function Switch() {
return _React$Component.apply(this, arguments) || this;
}
var _proto = Switch.prototype;
_proto.render = function render() {
var _this = this;
return React.createElement(context.Consumer, null, function (context) {
...
// 优先取得Switch的 location 这个 props
var location = _this.props.location || context.location;
var element, match;
// 遍历Switch组件的子组件进行比对 location.pathname 和 每个子组件的path 是否一致
React.Children.forEach(_this.props.children, function (child) {
if (match == null && React.isValidElement(child)) {
element = child;
var path = child.props.path || child.props.from;
match = path ? matchPath(location.pathname, _extends({}, child.props, {
path: path
})) : context.match;
}
});
// 返回最终符合path和location 一致的组件
return match ? React.cloneElement(element, {
location: location,
computedMatch: match
}) : null;
});
};
return Switch;
}(React.Component);
Switch如果传递props {pathname: 'xxx'},则会使用此 pathname进行匹配无论url是否改变,而如果没有传递此props则会使用Router中history监测得到的path
// Router 部分代码,详细见上面
_proto.render = function render() {
return React.createElement(context.Provider, {
value: {
history: this.props.history,
location: this.state.location, // 通过Content传递location给子组件
match: Router.computeRootMatch(this.state.location.pathname),
staticContext: this.props.staticContext
}
}, React.createElement(historyContext.Provider, {
children: this.props.children || null,
value: this.props.history
}));
};
Router将监测到的location作为Context传递给所有子组件,所以Switch 必须爆包含在 Router中才能使用到
遍历Switch的所有Children,调用matchPath函数对location.pathname和当前child的path这个props进行比对如果一致则返回对应的element,不是则返回null
Link
var Link = forwardRef(function (_ref2, forwardedRef) {
var _ref2$component = _ref2.component,
component = _ref2$component === void 0 ? LinkAnchor : _ref2$component, // 默认为LinkAnchor LinkAnchor 默认返回a标签
...
return React.createElement(__RouterContext.Consumer, null, function (context) {
...
var history = context.history; // 通过context传递下来的history ,这个也是在Router创建,如果是hashRouter或者BrowserRouter 时候赋值的
var location = normalizeToLocation(resolveToLocation(to, context.location), context.location); // 处理location的写法
var href = location ? history.createHref(location) : ""; //
var props = _extends({}, rest, {
href: href,
navigate: function navigate() {
var location = resolveToLocation(to, context.location);
var method = replace ? history.replace : history.push;
method(location);
}
}); // React 15 compat
if (forwardRefShim !== forwardRef) {
props.ref = forwardedRef || innerRef;
} else {
props.innerRef = innerRef;
}
return React.createElement(component, props); //最后返回包装后的LinkAnchor
});
});
function createHref(location) {
return basename + createPath(location);
}
function createPath(location) {
var pathname = location.pathname,
search = location.search,
hash = location.hash;
var path = pathname || '/';
if (search && search !== '?') path += search.charAt(0) === '?' ? search : "?" + search;
if (hash && hash !== '#') path += hash.charAt(0) === '#' ? hash : "#" + hash; // 如果有hash 则判断path是否添加 了# 没有添加
return path;
}
var LinkAnchor = forwardRef(function (_ref, forwardedRef) {
var innerRef = _ref.innerRef,
navigate = _ref.navigate,
_onClick = _ref.onClick,
rest = _objectWithoutPropertiesLoose(_ref, ["innerRef", "navigate", "onClick"]);
var target = rest.target;
// 合并Link传递过来的props
var props = _extends({}, rest, {
onClick: function onClick(event) {
try {
if (_onClick) _onClick(event);
} catch (ex) {
event.preventDefault();
throw ex;
}
if (!event.defaultPrevented && // onClick prevented default
event.button === 0 && ( // ignore everything but left clicks
!target || target === "_self") && // let browser handle "target=_blank" etc.
!isModifiedEvent(event) // ignore clicks with modifier keys
) {
event.preventDefault();
navigate(); // Link传递的navigate函数
}
}
});
...
return React.createElement("a", props);
});
当我们在HashRouter或者BrowserRouter 里面使用Link组件,会经历 Link , return React.createElement(component, props); 最后返回了一个React元素,createElement 里面的第一个元素是LinkAnchor,而 LinkAnchor 组件则会返回一个把Link里面href作为a标签href。 传递给a标签的href 是通过 var href = location ? history.createHref(location) : ""; 创建的
当hash改变的时候如何通知组件?
function setState(nextState) {
_extends(history, nextState);
history.length = globalHistory.length;
transitionManager.notifyListeners(history.location, history.action);
}

浙公网安备 33010602011771号