HashRouter与BrowserRouter
HashRouter:
原理:基于url的hash值,通过监听hash值的改变,从而模拟路由的跳转。
class HashRouter extends React.Component {
history = createHistory(this.props);
render() {
return <Router history={this.history} children={this.props.children} />;
}
}
BrowserRouter:
H5对history进行了增强,添加了pushState,replaceStae方法和popstate事件。
pushState是添加一个新的历史记录;repalceState是替换当前history;两者都不会导致刷新。
popstate事件则是当历史记录条目更改时,将会触发。
ps:需要注意的是调用history.pushState()或history.replaceState()不会触发popstate事件。只有在做出浏览器动作时,才会触发该事件,如用户点击浏览器的回退按钮(或者在Javascript代码中调用history.back()或者history.forward()方法)
利用pushState、repalceState实现前端路由跳转,通过对popstate事件的监听,从而补充了浏览器前进后退的功能,从而实现路由。
class BrowserRouter extends React.Component {
history = createHistory(this.props);
render() {
return <Router history={this.history} children={this.props.children} />;
}
}
在两者组件中,都是使用了history库,来实现路由的基本功能
history库
createHashHistory
//解析hash值得到path
function getHashPath() {
// We can't use window.location.hash here because it's not
// consistent across browsers - Firefox will pre-decode it!
const href = window.location.href;
const hashIndex = href.indexOf('#');
return hashIndex === -1 ? '' : href.substring(hashIndex + 1);
}
listen是实现路由监听,并对外暴露。transitionManager是由createTransitionManager创建,用于统一收集和处理listener;checkDOMListeners是用于创建和销毁hashchange事件。
function listen(listener) {
const unlisten = transitionManager.appendListener(listener);
checkDOMListeners(1);
return () => {
checkDOMListeners(-1);
unlisten();
};
}
function checkDOMListeners(delta) {
listenerCount += delta;
if (listenerCount === 1 && delta === 1) {
//创建hashchange监听事件
window.addEventListener(HashChangeEvent, handleHashChange);
} else if (listenerCount === 0) {
//销毁hashchange事件
window.removeEventListener(HashChangeEvent, handleHashChange);
}
}
//监听的事件处理
function handleHashChange() {
//拿到path
const path = getHashPath();
const encodedPath = encodePath(path);
//是否是相同path
if (path !== encodedPath) {
// Ensure we always have a properly-encoded hash.
replaceHashPath(encodedPath);
} else {
const location = getDOMLocation();
const prevLocation = history.location;
if (!forceNextPop && locationsAreEqual(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;
//path入栈
handlePop(location);
}
}
function handlePop(location) {
if (forceNextPop) {
forceNextPop = false;
setState();
} else {
const action = 'POP';
//通过transitionManager 进行确认是否跳转
transitionManager.confirmTransitionTo(
location,
action,
getUserConfirmation,
ok => {
if (ok) {
setState({ action, location });
} else {
revertPop(location);
}
}
);
}
}
//更新history,执行所有listener
function setState(nextState) {
Object.assign(history, nextState);
history.length = globalHistory.length;
transitionManager.notifyListeners(history.location, history.action);
}
createBrowserHistory
逻辑跟createHashHistory类似,具体处理上有些不同
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);
}
}
function handleHashChange() {
handlePop(getDOMLocation(getHistoryState()));
}
function handlePop(location) {
if (forceNextPop) {
forceNextPop = false;
setState();
} else {
const action = 'POP';
transitionManager.confirmTransitionTo(
location,
action,
getUserConfirmation,
ok => {
if (ok) {
setState({ action, location });
} else {
revertPop(location);
}
}
);
}
}
function setState(nextState) {
Object.assign(history, nextState);
history.length = globalHistory.length;
transitionManager.notifyListeners(history.location, history.action);
}
createTransitionManager
function createTransitionManager() {
let prompt = null;
function setPrompt(nextPrompt) {
warning(prompt == null, 'A history supports only one prompt at a time');
prompt = nextPrompt;
return () => {
if (prompt === nextPrompt) prompt = null;
};
}
//确认跳转
function confirmTransitionTo(
location,
action,
getUserConfirmation,
callback
) {
// TODO: If another transition starts while we're still confirming
// the previous one, we may end up in a weird state. Figure out the
// best way to handle this.
if (prompt != null) {
const result =
typeof prompt === 'function' ? prompt(location, action) : prompt;
if (typeof result === 'string') {
if (typeof getUserConfirmation === 'function') {
getUserConfirmation(result, callback);
} else {
warning(
false,
'A history needs a getUserConfirmation function in order to use a prompt message'
);
callback(true);
}
} else {
// Return false from a transition hook to cancel the transition.
callback(result !== false);
}
} else {
callback(true);
}
}
let listeners = [];
//添加listener,并返回一个删除listener的方法
function appendListener(fn) {
let isActive = true;
function listener(...args) {
if (isActive) fn(...args);
}
listeners.push(listener);
return () => {
isActive = false;
listeners = listeners.filter(item => item !== listener);
};
}
//执行所有的listener
function notifyListeners(...args) {
listeners.forEach(listener => listener(...args));
}
//返回创建的对象
return {
setPrompt,
confirmTransitionTo,
appendListener,
notifyListeners
};
}
export default createTransitionManager;
优劣
HashRouter:
优点:
是兼容性好,兼容到ie8,且大多数前端框架均有hash路由的实现
不需要服务端进行设置
除资源加载和ajax请求外,无其他请求
缺点:
对于部分需要重定向的操作,后端无法获取hash部分内容,导致后台无法取得url中的数据,典型的例子就是微信公众号的oauth验证
服务器端无法准确跟踪前端路由信息
对于需要锚点功能的需求会与目前路由机制冲突
BrowserRouter:
优点:
- 对于重定向过程中不会丢失url中的参数。后端可以拿到这部分数据
- 绝大多数前段框架均提供了browser的路由实现
- 后端可以准确跟踪路由信息
- 可以使用history.state来获取当前url对应的状态信息
缺点:
- 兼容性不如hash路由(只兼容到IE10)
- 需要后端支持,每次返回html文档(可以通过nginx代理映射到index.html)
参考:
React-Router 源码解析
你真的了解前端路由吗?
web前端hash路由的实现机制
React SPA 应用 hash 路由如何使用锚点
网友评论