从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{}里面的内容