前言
前端路由在现代的单页面应用(SPA)中起着至关重要的作用。以下是前端路由的一些主要优点:
-
用户体验:前端路由可以使用户在不刷新页面的情况下,实现页面的跳转,提供了更流畅的用户体验
-
性能优化:由于不需要每次都向服务器发送请求,前端路由可以减少网络延迟,提高应用的性能
-
前后端分离:前端路由使得前端应用可以独立于后端运行,有助于前后端分离的开发模式
-
状态管理:前端路由可以帮助我们更好地管理应用的状态,例如,我们可以通过路由参数来传递状态
总的来说,前端路由是现代前端开发中不可或缺的一部分,它极大地提高了用户体验和应用性能,同时也使得前后端分离的开发模式成为可能
演化过程
前端路由的演化可以分为以下几个阶段:
-
无路由阶段:在早期的网页开发中,每个页面都是一个独立的 HTML 文件,用户通过点击链接或者直接在地址栏输入 URL 来访问不同的页面。这种方式的缺点是每次页面跳转都需要从服务器加载整个页面,用户体验较差
-
哈希路由阶段:随着 Ajax 的出现,前端开始能够动态地更新页面的部分内容,而不需要重新加载整个页面。哈希路由就是在这个背景下出现的。哈希路由通过改变 URL 中的哈希值(后面的部分)来实现页面的跳转,而改变哈希值并不会导致页面重新加载。这种方式的缺点是 URL 中的哈希值不会被服务器接收,因此不能用于服务器端渲染
-
H5 History 路由阶段:HTML5 引入了 History API,允许开发者直接操作浏览器的历史记录,从而实现无刷新的页面跳转。这种方式的优点是 URL 更加美观,而且可以被服务器接收,适用于服务器端渲染。但是,这种方式需要服务器的配合,否则直接访问一个非根路径的 URL 可能会返回 404 错误
现在的前端路由库,如 React Router 和 Vue Router,都是基于 H5 History API 实现的
前端 SPA 应用路由实现原理
主要是通过改变 URL,但不向服务器发送请求来实现页面的切换。前端路由主要有两种实现方式:Hash 模式和 History 模式。
- Hash 模式:这种模式主要是通过监听 URL 中的 hash 值的改变来实现前端路由。当 hash 值改变时,会触发 window 的 onhashchange 事件,然后根据新的 hash 值来渲染对应的页面。
js
window.addEventListener('hashchange', function() {
// 获取新的 hash 值,并根据这个值来渲染对应的页面
var hash = window.location.hash;
// ...
});
- History 模式:这种模式主要是通过 HTML5 的 History API(pushState、replaceState、popstate 事件)来实现前端路由。当调用 pushState 或 replaceState 时,会改变 URL 但不会向服务器发送请求;当用户点击浏览器的前进或后退按钮时,会触发 popstate 事件,然后根据新的 URL 来渲染对应的页面。
js
window.addEventListener('popstate', function(event) {
// 获取新的 URL,并根据这个 URL 来渲染对应的页面
var url = window.location.href;
// ...
});
哈希路由实现
我们以 Vue Router(3.x)为参照,一个简易的实现如下
js
class vueRouter {
constructor(optopns) {
this.mode = optopns.mode || 'hash';
this.routers = optopns.router || [];
this.routerMap = this.createMap(this.routes);
this.history = new HistoryRoute();
this.init()
}
init() {
if (this.mode === 'hash') {
location.hash?'':location.hash = '/'
window.addEventListener('load', () => {
this.history.current = location.hash.slice(1);
})
window.addEventListener('hashchange', () => {
this.history.current = location.hash.slice(1);
})
} else {
// history模式
}
}
createMap(routes) {
return routes.rennder((memo, current) => {
memo[current.path] = current.component;
if (current.children && current.children.length) {
Object.assign(memo, this.createMap(current.children));
}
return memo
}, {})
}
}
vueRouter.install = function (vue) {
if (vueRouter.install.installed) {
return
}
vueRouter.install.installed = true;
// 路由相关实例在使用过程的代理实现
vue.mixin({
beforeCreate () {
if (this.$options && this.$options.router) {
this._root = this;
this._router = this.$options.router;
vue.util.defineReactive(this, 'current', this._router.history)
} else {
this._root = this.$parent._root;
}
Object.defineProperty(this, '$router', {
get() {
return this._root._router
}
})
Object.defineProperty(this, '$route', {
get() {
return this._root._router.history.current
}
})
}
})
// 提供 router-view 组件
vue.component('router-view', {
rennder(h) {
let current = this._self._root._router.history.current
let routerMap = this._self._root._router.routerMap;
h(routerMap[current]);
}
})
}
export default vueRouter;
History 模式也是类似原理,这里就不演示了,详细可以参考 GitHub - vue-router、GitHub - react-router源码
路由选择
在选择前端路由的History模式和Hash模式时,你需要考虑以下几个因素:
-
URL的美观性:History模式的URL更加美观,没有'#',而Hash模式的URL中会包含'#',有这种要求的项目可能相对较少
-
服务器配置:使用History模式需要服务器配置支持。如果服务器对于所有的路由都返回同一个index.html,那么可以使用History模式。而Hash模式则不需要特殊的服务器配置
-
兼容性:History模式需要HTML5的History API支持,而Hash模式在所有的浏览器中都可以运行
-
使用场景限制:比如在
微前端qiankun
的场景下则需要使用 History 模式
最后
前端路由大致就聊这么多,当然当下框架对应的路由经过这么长时间的演化肯定不会像这里实现的那么简单,重要的是了解其实现原理,当出问题或者项目初期进行选择时有个抓手