Router和解析常用库
使用和常规API
note
// globalHistory
**var globalHistory = window.history;**
**// hash handleHashChange
function handleHashChange() {
handlePop(getDOMLocation(getHistoryState()));
}**
hash和browser通用
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);
}
});
}
}
**// browser handleHashChange
function handleHashChange() {
var path = getHashPath();
var encodedPath = encodePath(path);
if (path !== encodedPath) {
// Ensure we always have a properly-encoded hash.
replaceHashPath(encodedPath);
} else {
var location = getDOMLocation();
var prevLocation = history.location;
if (!forceNextPop && locationsAreEqual$$1(prevLocation, location)) return; // A hashchange doesn't always == location change.
if (ignorePath === createPath(location)) return; // Ignore this change; we already setState in push/replace.
ignorePath = null;
handlePop(location);
}
}**
browser router做了一个判断,如果
var path = getHashPath();
var encodedPath = encodePath(path);
if (path !== encodedPath) {...}
这里的encodePath
encodePath: function encodePath(path) {
return path.charAt(0) === '!' ? path : '!/' + stripLeadingSlash(path);
},
function getHashPath() {
// We can't use window.location.hash here because it's not
// consistent across browsers - Firefox will pre-decode it!
var href = window.location.href;
var hashIndex = href.indexOf('#');
return hashIndex === -1 ? '' : href.substring(hashIndex + 1);
}
就是判断当前的hash路径是否可以被正确解析,如果可以
function replaceHashPath(path) {
window.location.replace(stripHash(window.location.href) + '#' + path);
} // 添加了hash的路由效果
否则
var location = getDOMLocation();
var prevLocation = history.location;
if (!forceNextPop && locationsAreEqual$$1(prevLocation, location)) return; // A hashchange doesn't always == location change.
if (ignorePath === createPath(location)) return; // Ignore this change; we already setState in push/replace.
ignorePath = null;
handlePop(location);
从Link出发看这两种模式的实现
var Link = forwardRef(function (_ref2, forwardedRef) {
var _ref2$component = _ref2.component,
component = _ref2$component === void 0 ? LinkAnchor : _ref2$component, // component 取值取决于是否为Link传入component 见下方 例-10
replace = _ref2.replace,
to = _ref2.to,
innerRef = _ref2.innerRef,
rest = _objectWithoutPropertiesLoose(_ref2, ["component", "replace", "to", "innerRef"]);
return React.createElement(__RouterContext.Consumer, null, function (context) {
!context ? process.env.NODE_ENV !== "production" ? invariant(false, "You should not use <Link> outside a <Router>") : invariant(false) : void 0;
var history = context.history;
var location = normalizeToLocation(resolveToLocation(to, context.location), context.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); // 返回一个element
});
});
// 例-10
const FancyLink = React.forwardRef((props, ref) => (
<a ref={ref} {...props}>💅 {props.children}</a>
))
<Link to="/" component={FancyLink} />
我们在Link上面添加的 to 、 replace 或者component的含义就是如此
- to 链接到某个地址 参数可以是[string, object, function]形式
- replace 跳转某个地址,但是不加入历史栈中
- component 自定义Link渲染出来的标签
我们来思考个问题? Link被点击会跳转到to 写的地址且正确拼接如何实现的?
其实就是LinkAnchor 这个组件中实现的下面我们来详细分析这个组件
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;
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();
}
}
}); // React 15 compat
if (forwardRefShim !== forwardRef) {
props.ref = forwardedRef || innerRef;
} else {
props.ref = innerRef;
}
/* eslint-disable-next-line jsx-a11y/anchor-has-content */
return React.createElement("a", props);
});
抽出点击事件的处理逻辑
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(); // 调用此函数
}
}
上面可以看出来如果没有设置点击事件的情况下,会走到navigate的函数,而这个函数是在Link组件中定义的,对应如下
navigate: function navigate() {
var location = resolveToLocation(to, context.location); // 获取to的值
var method = replace ? history.replace : history.push; // 判断是替换还是压入栈中
method(location);
}
// 对应的resolveToLocation
var resolveToLocation = function resolveToLocation(to, currentLocation) {
return typeof to === "function" ? to(currentLocation) : to;
};
比如我们 的Link组件是 Home,则navigate对应的location 就是 '/'
上面的总结下来就是假设我们有 <Link to="/">Home</Link> 会经历如下步骤:
-
对应的HashRouter或者BrowserRouter 将生成的history 作为context中的history传递到Link组件中
- 对应生成的history的不同 主要在push这类会更改location的方法上面
- Hash模式下
var path = createPath(location); var encodedPath = encodePath(basename + path); var hashChanged = getHashPath() !== encodedPath; if (hashChanged) { ... pushHashPath(encodedPath); ... } // 相关的方法 var basename = props.basename ? stripTrailingSlash(addLeadingSlash(props.basename)) : ''; function stripTrailingSlash(path) { return path.charAt(path.length - 1) === '/' ? path.slice(0, -1) : path; } function pushHashPath(path) { window.location.hash = path; }- history(BrowserHistory)下
if (canUseHistory) { globalHistory.pushState({ key: key, state: state }, null, href); if (forceRefresh) { window.location.href = href; } else {...} } -
Link组件经过包装LinkAnchor后提供了组合后的默认a标签的组件添加

浙公网安备 33010602011771号