从0实现一个vue-router
我们在使用vue-router时不免有如下的疑问:
1. 这个router插件内部到底实现了什么,才使得页面跳转无刷新?
2. 为什么要把router实例加入到Vue配置项中?
3. 为什么使用<router-view>和<router-link>等不需要注册?
4. 为什么我点了<router-link>后<router-view>的内容能正常切换?
5. 为什么this.$router.push能改变url地址?

那么带着问题,希望你通读全文,我想你会找到想要的答案的(假装这些问题都是你问的~)
我们知道,每次我们在做vue项目时需要使用router时:
1.首先都会在路由文件的index.js文件中声明:
Vue.use(VueRouter) //使用VueRouter这个插件
2.接着我们需要在index.js中创建一个实例:
const router = new VueRouter({
mode: 'hash',
base: process.env.BASE_URL,
routes
})
3. 接下来我们在main.js中把这个router实例放入到Vue实例中:
new Vue({
el: '#app',
router,//在Vue实例中放入这个router实例
components: { App },
store,
template: '<App/>'
})
接着我们就可以用了,ok全文结束,goodbye!我去弹我的尤克里里去了~
(读者:做咩野啊~?)

(呃额呃呃别打了哥,疼疼疼,我说我说我都说,今天不给各位讲的明明白白我不走了好吧)
停止闲扯,重点来了:
我们仔细想想,我们如果要自制一个router,那需要实现些什么?首先vue-router是通过Vue.use引入的,说明它是一个插件,这个插件内肯定实现了router-view和router-link两个组件的编译模板。我们先配置好一个自己的router文件,如下:

在main.js中引入
import router from './myRouter'
在myRouter文件中的index.js添加
import VueRouter from './myvue-router'
做好前置工作,接下来,我们就来开发一个插件,首先翻看vue文档,发现对开发插件有着详尽的说明:

第一个参数是一个Vue,在复杂的数据流中我们就需要通过像that=this一样把这个宝贵的Vue保存起来,同时防止打包时把vue实例打包进插件里面,接下来我们要用到mixin的全局引入,老规矩,先看文档:

再观察我们的Vue实例配置:
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
可见,我们可以通过this.$options.router访问到Vue实例中的router,那么,如果这个选项存在,那么我们就可以在vue原型上挂载一个$router选项,那么我们就能通过this.$router访问到vue的router实例了,是不是很妙!
但别忘了在install中还有重要的一步就是实现router-link和router-view,router-link实际上是一个不发生跳转的超链接,即它的href为’#’拼接上router-link标签中的to的值,我们把to放入props中传给这个component即可实现组装render出一个点击可跳转的链接,但是router-link中的名称怎么渲染呢?唉~同学你忘了作用域插槽啦,通过this.$slots.default访问到这个标签的默认内容,我的router-link中想写什么就写什么,不怕render不出来哈哈。
至于router-view,可能稍微有些复杂。大家想,为什么我们点击router-link链接能自动将router-view内的内容自动更新?一定是router-view中有某种获取url改变的神奇方法,并将改变后的路由的配置对象与routes中现存的路径进行匹配,将匹配成功的配置对象追加到渲染的组件上,那么我们就可以通过一个响应式的变量current来存储这个目前选中的路径值(用Vue.util.defineReactive来实现响应式),再在整个$options配置项中的路由项中查找是否有相同的路径值,将这个匹配成功的route的component配置render并return出去。至此一个不支持嵌套子路由但支持主路由的router源码就实现啦(嵌套子路由后面再实现)具体源码如下:
//目标:
//1.实现插件
//2.两个组件
// vue插件:function 必须有一个install,会被Vue.use调用
let Vue //保存Vue构造函数,插件中使用
class VueRouter {
constructor(options) {
//保存当前选项
this.$options = options
//current是 vuerouter的一个实例属性
const initial = window.location.hash.slice('#') || '/';//因route中的配置对象都为'/Home'的形式,而此时的window.location.hash为'#/Home'的形式,所以需要截掉#
// //Vue插件开发一系列api,目的是实现数据响应式,即将router-view中依赖于current的数据在current发生变化时也重新渲染
Vue.util.defineReactive(this, 'current', initial)
// 监听hash变化
window.addEventListener('hashchange', () => {
this.current = window.location.hash.slice(1)//截取#后的内容
})
}
}
//_Vue是Vue.use调用时传入的
VueRouter.install = function (_Vue) {
// 保存一下输入,同时防止打包时把vue实例打包进插件里
Vue = _Vue
// 通过全局混入,延迟下面的逻辑到router创建完毕并且附加到选项上才执行
Vue.mixin({
created () {
//此钩子在每个组件创建实例时都会调用,根实例才有此选项
if (this.$options.router) {//this.$options指new Vue时的选项
Vue.prototype.$router = this.$options.router
}
}
})
// 2.注册实现两个组件router-view,router-link
Vue.component('router-link', {
props: {
// 拿到router-link中的to属性
to: {
type: String,
required: true
},
},
render (h) {
// 拿到当前的hash地址,传给href
return <a href={'#' + this.to}>{this.$slots.default}</a>
}
})
Vue.component('router-view', {
// 渲染函数
render (h) {
// 先设置一个component
let component = null
const route = this.$router.$options.routes.find(
// 拿出上面计算得出的current来匹配route选项
(route) => route.path === this.$router.current
)
if (route) {
// 使component获取组件配置对象
component = route.component
}
// 返回该component的虚拟dom
return h(component)
}
})
}
export default VueRouter
实现效果:
引入的是myRouter文件里的router哈:

效果如下:

至此一个简易的vue-router源码就实现啦~作为一个21岁的前端小白,大四在读实习生,如果有写得不对的地方还望各位能不吝赐教,小生定当俯首恭听,感恩戴德~
附加提醒:
1. routes是什么:

2. 路由的router-link和router-view使用方式提示:

3. $optins是什么?

就是这个new Vue{}里面的内容
浙公网安备 33010602011771号