前言
Vue-router,也就是路由,相信大家对它都不会陌生。本篇文章用于记录自己学习VueRouter实现的过程,大家跟我一起来看看吧。 路由的基本使用VueRouter作为Vue全家桶重要组成部分,基本每个项目都会用它,因为Vue是单页面应用,可以通过路由来实现切换组件,达到切换页面的效果。具体实现分为以下3部分
- 1、引入
vue-router,并使用Vue.use(VueRouter) - 2、定义路由数组,并将数组传入
VueRouter实例,创建VueRouter实例,并将实例导出 - 3、将
VueRouter实例引入到main.js,并注册到根Vue实例上
实现思路
- 1、实现一个
VueRouter类,该类里具体实现以下功能-
- 处理路由选项
-
- 监控url变化(本文以hash模式为例)
-
- 响应这个变化
class VueRouter { constructor(options) { // 1. 保存路由选项 this.$options = options // 此种写法,current非响应式 // this.current = window.location.hash.slice(1) || '/' // 如何让current成为一个响应式数据?非响应式数据 current改变并不能触发h函数重新渲染组件 // Vue.set()要求第一个参数必须是一个响应式数据对象 // Vue内置方法此方法可以给一个对象指定一个响应式属性 Vue.util.defineReactive(this, 'current', window.location.hash.slice(1) || '/') // 2. 监控hash变化 window.addEventListener('hashchange', () => { // hash: #/about this.current = window.location.hash.slice(1) }) } } -
- 2、实现
install方法,该方法里具体实现以下功能-
- 注册
$router,让所有的组件实例都可以访问到他
- 注册
-
- 注册两个全局组件
router-link,router-view
- 注册两个全局组件
Vue.use(VueRouter)执行时,会执行内部的install方法,传入Vue的构造函数,可以对其进行扩展- 需要考虑的问题
- 1).
$router挂载时机问题,如果直接使用Vue.prototype.$router=router的话,因为Vue.use(VueRouter)的执行时机早于new VueRouter()和new Vue(),所以此时是拿不到router实例的。考虑使用mixinVue.mixin({ beforeCreate() { // 组件实例化的时候执行 // this 是组件实例 $options 每个组件实例的$options可以得到当前组件的配置信息 if (this.$options.router) { // 如果存在说明是根实例 // 该行代码需要延迟执行,延迟到router实例和vue实例都创建完毕 Vue.prototype.$router = this.$options.router } } })
- 1).
-
- 注册两个全局组件
router-link,router-view// <router-link to="/home">Home</router-link> Vue.component("router-link", { props: { to: { type: String, required: true, }, }, render(h) { // <a href="to">xxx</a> // return <a href={'#'+this.to}>{this.$slots.default}</a> return h( "a", { attrs: { href: "#" + this.to, }, }, this.$slots.default // 匿名插槽的所有内容 ); }, });Vue.component("router-view", { render(h) { /* 1. 获取当前url的hash部分 2. 根据hash部分从路由表中获取对应的组件 */ // 获取当前路由对应的组件 let component = null // 当前this就是当前router-view组件的实例 this.$router存在是因为在上面mixin混入的时候 在所有组件身上都挂在了$router // this.$router.current当前路由器实例 this.$router.$options.routes是new VueRouter中传递过来的routes映射关系 const route = this.$router.$options.routes.find( (route) => route.path === this.$router.current ); if (route) { component = route.component } console.log(this.$router.current, component); return h(component); }, })
- 注册两个全局组件
-
总结 以上就是实现一个简易版
VueRouter的过程,因为写的是个demo,暂未考虑嵌套路由等问题。希望大佬们多多指教。 完整代码
// src/myrouter/myvue-router.js
/*
自己创建一个router 声明自己的VueRouter
*/
let Vue;
class VueRouter {
constructor(options) {
// 1. 保存路由选项
this.$options = options
// this.current = window.location.hash.slice(1) || '/'
// 如何让current成为一个响应式数据 非响应式数据 current改变并不能触发h函数重新渲染组件
// Vue.set()要求第一个参数必须是一个响应式数据对象
Vue.util.defineReactive(this, 'current', window.location.hash.slice(1) || '/') // 此方法可以给一个对象指定一个响应式属性
// 2. 监控hash变化
window.addEventListener('hashchange', () => {
// hash: #/about
this.current = window.location.hash.slice(1)
})
}
}
// 参数1 Vue的构造函数 用形参传入进来而不使用import vue导入进来 可以优化打包进程 (写个插件把vue打包进来不合适)
VueRouter.install = function (_Vue) {
// 传入构造函数 能对其进行扩展
Vue = _Vue
// 1. 注册$router 让所有的组件实例都可以访问到他
// 混入 -- 当你想访问vue实例或组件实例的时候 都需要混入 在混入的时候获取到this 组件实例
Vue.mixin({
beforeCreate() { // 组件实例化的时候执行
// this 是组件实例 $options 每个组件实例的$options可以得到当前组件的配置信息
if (this.$options.router) {
// 如果存在说明是根实例
Vue.prototype.$router = this.$options.router // 该行代码需要延迟执行,延迟到router实例和vue实例都创建完毕
}
}
})
// 2. 注册两个全局组件 router-link router-view
// <router-link to="/home">Home</router-link>
Vue.component("router-link", {
props: {
to: {
type: String,
required: true,
},
},
render(h) {
// <a href="to">xxx</a>
// return <a href={'#'+this.to}>{this.$slots.default}</a>
return h(
"a",
{
attrs: {
href: "#" + this.to,
},
},
this.$slots.default // 匿名插槽的所有内容
);
},
});
Vue.component("router-view", {
render(h) {
/*
1. 获取当前url的hash部分
2. 根据hash部分从路由表中获取对应的组件
*/
// 获取当前路由对应的组件
let component = null
// 当前this就是当前router-view组件的实例 this.$router存在是因为在上面mixin混入的时候 在所有组件身上都挂在了$router
// this.$router.current当前路由器实例 this.$router.$options.routes是new VueRouter中传递过来的routes映射关系
const route = this.$router.$options.routes.find(
(route) => route.path === this.$router.current
);
if (route) {
component = route.component
}
console.log(this.$router.current, component);
return h(component);
},
});
}
export default VueRouter
// src/myrouter/index.js
import Vue from 'vue'
import VueRouter from './myvue-router'
import Home from '../views/Home.vue'
/* 1.VueRouter是一个插件?
内部做了什么:
1)实现并声明两个组件router-view router-link
2) install: this.$router.push()
*/
/*
vue插件有两种形态 fn object
Vue.use会调用插件的install(Vue)方法 传递的对象为Vue的构造函数
*/
Vue.use(VueRouter)
const routes = [
// path和compent存在映射关系
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
}
]
// 2.创建实例
// 重点 在上面Vue.use(VueRouter)调用install方法的时候 new VueRouter() 还没有执行 意味着这个VueRouter实例目前还不存在
const router = new VueRouter({
mode: 'hash',
base: process.env.BASE_URL,
routes // 传递给VueRouter
})
export default router
// src/main.js
import Vue from 'vue'
import App from './App.vue'
import router from './myrouter'
Vue.config.productionTip = false
// 事件总线
Vue.prototype.$bus = new Vue()
// Vue实例化, 在vueRouter实例创建完成后创建Vue实例 把router,store作为选项传入
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')