前言
本文主要介绍 Vue Router 的核心原理以及如何实现一个简易版本的 Vue Router(history 和 hash 模式)。
前置知识
hash
和 history
模式区别
-
表现形式上:hash 模式 url music.163.com/#/playlist?... , 带有 # 号。
-
原理区别:
hash
模式基于锚点,以及onhashchange
事件。history
模式是基于HTML5
中的history
模式。history.pushState、replaceState
在 IE10 以后才支持,存在兼容性问题。push
会向服务器发送请求,使用pushState
不会发送请求,但是会在浏览器端产生历史记录,形成客户端路由。
history
模式使用
-
该模式需要服务器端的支持。
-
单页应用中,服务端不存在 www.testurl.com/login,地址会返回 404,提示找不到页面。
-
history
模式下前后端工作过程:history 模式下,刷新页面会向服务器进行网络请求,后端处理history
模式,需要将默认的html
文件返回给前端,前端获取到文件后再根据路由自行处理。
Vue Router
使用方式
比较常规的使用方式,避免文章篇幅过长,这里就不一一赘述了。基础步骤如下:
- 在
Vue 2.6.11
模板项目中, 安装依赖:
node
npm install vue-router
src
目录下创建router
文件夹,并新建一个index.js
,内容如下:
javascript
import Vue from "vue";
import VueRouter from "vue-router";
import Home from "../components/Home";
import ComponentA from "../components/ComponentA";
Vue.use(VueRouter);
const routes = [
{
path: "/",
redirect: "/home",
component: Home
},
{
path: "/componentA",
name: "ComponentA",
component: ComponentA
},
];
const router = new VueRouter({
mode: "history",
routes
});
export default router;
- 在
main.js
中引入,在Vue
实例化时,作为Options
参数传递给Vue
的构造函数。
javascript
import Vue from "vue";
import App from "./App.vue";
import router from "./router/index";
Vue.config.productionTip = false;
const vm = new Vue({
router,
render: (h) => h(App)
}).$mount("#app");
console.log("vm: ", vm);
router
实例对象
Vue 实例化后,router 对象作用在何处? 我们在 main.js
中 Vue 实例化后的位置添加一行打印,看看 Vue
实例化后的对象上有什么。
在 vm.$options.router
中,我们看到了许多熟悉的对象属性与官方文档上提到的编程式导航方法。
实现 history
模式
CustomRouter
类
这里我们给自己的 Router
类命名为 CustomRouter
,定义如下:
javascript
export default class CustomRouter {
static install(Vue) {}
constructor(options) {}
init() {}
createRouteMap() {}
initComponents(Vue) {}
initEvent() {}
}
CustomRouter
类中的各个函数作用依次如下:
install
函数,Vue 插件需要对外暴露的install
方法,关于 Vue 插件开发,可以参考官方文档:cn.vuejs.org/v2/guide/pl... 。constructor
函数:构造函数。init
函数:全局初始化。createRouteMap
:创建路由配置与对应组件的映射,属性值是对应的组件。initComponents
:初始化Router-View
和Router-Link
组件。initEvent
: 监听浏览器history
的popstate
事件,当页面 URL 变化时,将会触发监听事件。
constructor
javascript
constructor(options) {
this.options = options;
this.routeMap = {};
this.data = _Vue.observable({
current: "home"
});
}
构造函数中的对象成员如下:
-
options
:options
中存储的是路由相关的配置。 -
routeMap
: 路由path
与component
的映射,是一个对象,对象属性值为path
,对象值为component
,在createRouteMap
函数中进行初始化。 -
data:通过
2.6.x
版本后提供的observable
api,将对象转换成响应式。
initEvent
函数
监听 popstate 事件,并更新当前的 current。
javascript
initEvent() {
window.addEventListener('popstate', () => {
this.data.current = window.location.pathname;
})
}
initComponent
javascript
initComponents(Vue) {
/**
* Router-Link 组件
*/
Vue.component("router-link", {
props: {
to: String
},
render(h) {
return h(
"a",
{
// dom 对象属性
attrs: {
href: this.to
},
on: {
click: this.clickHandler
}
},
[this.$slots.default]
);
},
methods: {
clickHandler(e) {
window.history.pushState({}, "", this.to);
this.$router.data.current = this.to;
e.preventDefault();
}
}
});
/**
* Router-View 组件
*/
const self = this;
Vue.component("router-view", {
render(h) {
const component = self.routeMap[self.data.current];
return h(component);
}
})
}
当
this.data.current
变化时,所有依赖于该变量的组件(router-view)都会重新渲染,这也是为什么要使用Vue.observable
的原因。
init
分别初始化 routeMap
、Router-View
、Router-Link
组件和 popstate
事件监听器。
javascript
init() {
this.createRouteMap();
this.initComponents();
this.initEvent();
}
install
javascript
static install(Vue) {
if (CustomRouter.install.installed) {
return;
}
CustomRouter.install.installed = true;
_Vue = Vue;
_Vue.mixin({
beforeCreate: function () {
if (this.$options.router) {
_Vue.prototype.$router = this.$options.router;
this.$options.router.init();
}
}
});
}
实现 hash
模式
hash
模式整体代码实现上和 history
基本一致,它监听的是 load
和 hashchange
事件,并在 router-link
组件点击时,设置 window.location
为 hash
形式。差异代码如下:
router-link
javascript
Vue.component("router-link", {
props: {
to: String
},
render(h) {
return h(
"a",
{
attrs: {
href: "#" + this.to
},
on: {
click: this.clickHandler
}
},
[this.$slots.default]
);
},
methods: {
clickHandler(e) {
window.location.hash = "#" + this.to;
this.$router.data.current = this.to;
e.preventDefault();
}
}
});
a 标签的 href 修改为了 hash 形式,clickHandler 点击事件处理中,window.location.hash 会被赋值为对应路由的 hash 形式。
hashchange
和 load
事件监听
javascript
onHashChange() {
if (!window.location.hash) {
window.location.hash = "#/";
}
this.data.current = window.location.hash.substr(1);
}
initEvent() {
window.addEventListener("load", this.onHashChange.bind(this));
window.addEventListener("hashchange", this.onHashChange.bind(this));
}
监听
hashchange
和load
事件。
代码示例
history
模式:historyMode.js。
hash
模式:hashMode.js。