在 Vue 中手动实现一个 vue-router
本文要点:
- 了解单页面应用 router 路由的原理。
- 通过实战手动实现一个 vue-router 加深单页面路由的理解。
单页面应用 router 路由实现原理
单页面应用可通过 hash 和 history 两种方式实现路由。实现路由要思考两个问题。第一、如何获取当前路径;第二、如何监听当前路径变化。
1. hash
hash 的方式即浏览器地址栏中含有 # 标志,该标志后面的部分即为 hash 值。示例:http://localhost:8080/#/index
1.1 hash 实现路由的原理
- 地址栏中 # 后面的值即为 hash 值。该 hash 值通常为路径 path,可通过
location.hash
获取当前路径。 - 监听当前路径变化的方法为
onhashchange
。
1.2 hash 实现路由的核心步骤
通过 hash 方式在 Vue 中实现一个简易路由,核心步骤如下:
- 获取路由参数 options;
- 初始化路由集合;
- 初始化当前路径,即默认路径;
- 当 load 时 和 hash 改变时监听 url 变化(主要是监听 hash 的变化)。
- 解析路由配置,即将路径 path 和组件 component 一一对应。
- 通过
Vue.component
方法将当前组件挂载到 Vue 中。 - 通过
Routes.install
和Vue.mixin
方法将 Vue 实例注入到当前实例中。在Vue.mixin
中添加beforeCreate()
路由钩子函数,在 Vue 执行该钩子函数回调时会一起调用启动路由。
1.3 hash 实现路由的完整代码
routes.js
该 js 是实现路由的核心过程
import Vue from 'vue'
export default class Routes {
constructor(options) {
this.$options = options;
this.routeMap = {};
// 路由响应式
this.app = new Vue({
data: {
//初始化路由为 /
current: "/"
}
});
}
init() {
this.bindEvents(); //监听url变化
this.createRouteMap(this.$options); //解析路由配置
this.initComponent(); // 实现组件
}
bindEvents() {
window.addEventListener("load", this.onHashChange.bind(this));
window.addEventListener("hashchange", this.onHashChange.bind(this));
}
onHashChange() {
//获取当前的 hash 值
this.app.current = window.location.hash.slice(1) || "/";
}
createRouteMap(options) {
//遍历路由,获取每个 path 对应的组件
options.routes.forEach(item => {
this.routeMap[item.path] = item.component;
});
}
initComponent() {
// <router-view-vue></router-view-vue>
Vue.component("router-view-vue", {
render: h => {
//hash 改变时组件也会改变
const component = this.routeMap[this.app.current];
return h(component);
}
});
}
}
Routes.install = function (Vue) {
// 混入
Vue.mixin({
beforeCreate() {
// 这里的 this 是 Vue 实例
if (this.$options.router) {
// 仅在根组件执行一次,添加 push 方法
Vue.prototype.$router = this.$options.router;
Vue.prototype.$router.push = function (options) {
window.location.hash = "/"+options.name;
};
this.$options.router.init();
}
}
});
};
router.js
该 js 是配置路由数组
import Index from './Index.vue';
import Login from './Login.vue';
const router = [
{path:'/',name:"index",component:Index},
{path:'/login',name:"login",component:Login},
];
export default router;
main.js
创建 Vue 实例
import Vue from 'vue'
import App from './App.vue'
import routes from './router'
import Routes from './routes';
//阻止显示生产模式的提示
Vue.config.productionTip = false;
//使用路由
Vue.use(Routes);
//创建路由实例
const router = new Routes({
routes // routes: routes 的简写
});
new Vue({
render: h => h(App),
router
}).$mount('#app')
App.vue
组件入口(根组件)
<template>
<div id="app">
<router-view-vue/>
</div>
</template>
<script>
export default {
name: 'App',
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
Index.vue
首页
<template>
<div>
<h3>首页</h3>
<div @click="goLogin">
去登录
</div>
</div>
</template>
<script>
export default {
name: 'Index',
methods: {
goLogin(){
this.$router.push({name: 'login'});
}
}
}
</script>
Login.vue
登录页
<template>
<h3>
登录页
</h3>
</template>
2. history
history 的方式是浏览器地址栏中不含有 # 标志。示例:http://localhost:8080/index
2.1 history 实现路由的原理
- history 是 HTML5 新增的 API 。history.pushState 方法和 history.replaceState 方法可以改变 url 而不发生请求。通过
location.pathname
可以获取当前路径。 - 监听浏览器前进、后退的方法为
onpopstate
,而监听当前路径变化需要注册一个新的全局监听事件。
1.2 history 实现路由的核心步骤
通过 history 方式在 Vue 中实现一个简易路由,核心步骤如下:
同 hash 方法一样,不再赘述。
1.3 history 实现路由的完整代码
同 hash 方法大致一样,以下列出不同之处。
- 获取当前路径。在 history 中是通过 location.pathname 获取当前路径。
bindEvents() {
window.addEventListener("load", this.onPushState.bind(this));
window.addEventListener("pushState", this.onPushState.bind(this));
window.addEventListener("popstate", this.onPushState.bind(this));
}
onPushState() {
//获取当前路径
this.app.current = location.pathname || "/";
}
- 添加 pushState 方法的监听。因为没法通过执行 pushState 来监听路由变化。所以只有重写 pushState 方法,在其内部注册一个全局的监听方法。
var _wr = function(type) {
var orig = history[type];
return function() {
var rv = orig.apply(this, arguments);
var e = new Event(type);
e.arguments = arguments;
window.dispatchEvent(e);
return rv;
};
};
history.pushState = _wr('pushState');
- push 方法。在 history 中是通过 history.pushState 来实现 push 方法。
Vue.prototype.$router.push = function (options) {
history.pushState(null,null,'/'+options.name);
};