仿写vue-router,超详细的逐行注释

目标自己手动实现一个类似于vue-router的路由插件,Let’s go。

先上效果图,能够实现组件切换,且路由发生了变化。

效果图
接下来,让我们一起来实现这个vue-router路由组件切换功能。

首先你需要对es6有一定的了解,因为里面会有es6的语法,更重要的是es6定义的类,如果不熟悉,那么请看我的这篇文章,里面有es6关于类的定义,其次也需要你对vue也有一定的认知。如果你都有啦,那么我们就愉快的开始往下吧。

本文采用的是vue-cli脚手架生成的项目,src文件目录如下

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已经完毕。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值