目标自己手动实现一个类似于vue-router的路由插件,Let’s go。
先上效果图,能够实现组件切换,且路由发生了变化。
接下来,让我们一起来实现这个vue-router路由组件切换功能。
首先你需要对es6有一定的了解,因为里面会有es6的语法,更重要的是es6定义的类,如果不熟悉,那么请看我的这篇文章,里面有es6关于类的定义,其次也需要你对vue也有一定的认知。如果你都有啦,那么我们就愉快的开始往下吧。
本文采用的是vue-cli脚手架生成的项目,src文件目录如下
k-router文件下的index.js就是实现vue-router的核心,像正常的vue项目一样,对组件进行注册。
import Vue from 'vue'
// import Router from 'vue-router'
import Krouter from '../k-router/index.js'
// Vue.use(Router)
Vue.use(Krouter)
const router = new Krouter({
routes: [
{
path: '/',
name: 'HelloWorld',
component: () => import('@/components/HelloWorld'),
// 单个路由导航守卫
beforeEach (to, from, next) {
next();
}
},
{
path: '/about',
name: 'About',
component: () => import('@/components/About')
}
]
})
// 注册全局路由导航守卫
router.beforeEach = (to, from, next) => {
next();
}
router.beforeEach()
export default router
注释掉vue项目本身的vue-router模块,换上我们自己的,路由表依然像正常注册即可。
接下来就是重头戏,k-router里面的实现,超详细的逐行注释。
let Vue; // 声明一个全局变量用来缓存传过来的vue对象
class Krouter { // 定义我们自己的krouter类
// 这里说一下,vue插件至少需要提供一个install方法,为什么呢?
// 因为通过vue.use(插件)进行插件注册时,执行的就是install方法。你可以理解成
// vue.use(krouter) => install () { } 调用插件内部的install方法
static install (_vue) { // static表面这个方法属性类所有,而不是通过new创建的实例化对象
Vue = _vue; // 将传过来的vue实例进行缓存
Vue.minxin({ // 使用vue本身的混入对vue实例进行扩展
beforeCreate () { // 对beforeCreate生命周期进行扩展
// 因为混入的原因,每个vues实例都会执行,所以我们要进行筛选,仅在第一次初始化路由表时执行
// 因为我们在入口文件那里对路由表进行初始化,所以只有入口文件那里拥有router对象。
if (this.$options.router) { // this.$options拿到当前vue实例内的配置项
this.$options.router.init(); // 执行init初始化方法
// 仿this.$router,把new创建的路由对象挂载到vue实例上,这样可以通过this.$krouter直接访问Krouter的属性和方法
Vue.prototype.$krouter = this.$options.router;
}
}
})
}
// 类的constructor
contructor (options) { // 接收通过new Krouter(路由表)传进来的路由表
this.options= options; // 对实例化对象进行赋值
// 因为路由表是数组形式,而我们需要根据路由对象也就是浏览器中的hash进行渲染对应组件,
// 所以这里我们需要对路由表稍微处理下,转成 路由 => 组件 这种key-value形式
this.routesMap= {};
// 使用vue本身的mvvm,初始化一个vue实例
this.$app = new Vue({
data(){
return {
// 默认path
current: window.location.hash.slice(1) || '/'
}
}
})
}
init () { // 初始化方法
// 处理路由表
this.manageRoutes();
// 绑定事件
this.bindEvents();
// 渲染默认component
this.renderComponent()
}
manageRoutes () { // 对路由数组进行加工
// 循环路由数组,转成key-value形式,path => component
return this.options.forEach(item => this.routesMap[item.path] = item.component);
}
// 利用vue的组件注册进行组件渲染
renderComponent () {
// 渲染router-link,接收跳转的path
Vue.component('router-link', {
// 通过组件传值拿到path
props: {
to: {
type: String,
required: true
}
},
// 利用render函数进行dom渲染,渲染为a标签
render(h){
return h('a', {
// 为a标签添加href跳转属性,路径为传值拿过来的path
attrs: {
href: '#' + this.to
}
// 使用vue插槽的方法,拿到插槽默认内容即router-link标签所包裹的文字
}, this.$slots.default)
}
})
// 渲染router-view 也就是组件显示的地方
Vue.component('router-view', {
render: h => {
// 利用vue的响应原理,动态切换所渲染的组件模版
return h(this.routesMap[this.$app.current].component)
}
})
}
// 利用hashchange来监听hash变化,渲染对应的组件
bindEvents () {
// 在onHashChange事件里,this指向会发生改变,指向Event对象,所以这里绑定当前实例化对象传入onHashChange事件里
window.addEventListener('hashchange', this.onHashChange.bind(this));
}
// hash变化时,更改当前hash
onHashChange (e) {
// 获取当前router实例
let router = this.routesMap[this.getHash()];
// 单个路由是否定义了路由导航守卫
if (router.beforeEach) {
// 定义了路由导航守卫,调用路由表中的导航守卫,beforeEach,第一个参数传要去的path,
// 第二个参数传过来的path,然后定义一个next函数,一定要调用才会渲染组件
router.beforeEach(e.newURL.split('#')[1], e.oldURL.split('#')[1], () => {
this.$app.current = this.getHash()
})
} else {
// 没有则正常跳转组件
this.$app.current = this.getHash()
}
// 全局的路由导航守卫
if (this.beforeEach) {
this.beforeEach(e.newURL.split('#')[1], e.oldURL.split('#')[1], () => {
this.$app.current = this.getHash()
})
}
}
// 获取当前浏览器的hash值,防止刷新重置
getHash () {
// 获取hash值,取不到则默认为首页
return window.location.hash.slice(1) || '/'
}
// 路由push跳转,仿造this.$router.push(path)
push(url) {
// 直接改变浏览器中的hash
window.location.hash = url;
}
}
好了krouter的核心功能写完了,接下来就是如何使用
像使用vue-router一样使用就好了,上代码
<template>
<div id="app">
<!-- 点击事件调用路由的push方法 编程式跳转 -->
<p @click="jumpTo">about</p>
<!-- router-link 声明式跳转 -->
<router-link to="/about" tag="p">about组件</router-link>
<router-link to="/" tag="p">helloworld组件</router-link>
<!-- 组件显示的地方 -->
<router-view/>
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
message: 'hello vue'
}
},
methods: {
jumpTo() {
console.log(this.$krouter)
this.$krouter.push('/about')
}
}
}
</script>
<style>
</style>
至此,仿写vue-router已经完毕。