1

vue-router大家一定都不陌生,这个简单,引用一下就好。但是我们要有知其然并且知其所以然的态度,今天就来带大家手动实现下vue-router。

简介

Vue Router 是 Vue.js 官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。包含的功能有:

  • 嵌套的路由/视图表
  • 模块化的、基于组件的路由配置
  • 路由参数、查询、通配符
  • 基于 Vue.js 过渡系统的视图过渡效果
  • 细粒度的导航控制
  • 带有自动激活的 CSS class 的链接
  • HTML5 历史模式或 hash 模式,在 IE9 中自动降级
  • 自定义的滚动条行为

应用步骤

首先安装:vue add router

第一步:一般在router文件夹index文件中引用router插件

import Router from 'vue-router'
Vue.use(Router)

这里有个小问题为什么用use方法,因为vueRouter是插件,在vue中使用插件必须要用use方法。

第二步:创建Router实例,router.js

export default new Router({...})

第三步:在根组件上添加该实例,main.js

import router from './router'
new Vue({
 router,
}).$mount("#app")

第四步:添加路由视图,App.vue

<router-view></router-view>

这样就可以使用vue-router了,下面延展两个问题。
this.$router可以访问Router实例,并且router-view和router-link两个全局组件可以直接使用。
那么分析一下可以知道,插件引用后,相当于在用原型方式在全局注册了$router方法,内部肯定注册了Vue.prototype.$router,同时注册了两个全局变量。

手写vue-router

上面简单介绍了该如何使用vur-router,下面我们来手动实现下vue-router源码。
首先我们需要分析下vue-router做了什么。

1、作为一个插件存在:实现VueRouter类和install方法。
2、实现两个全局组件:router-view用于显示匹配组件内容,router-link用于跳转。
3、监控url变化:监听hashchange或popstate事件。
4、响应最新url:创建一个响应式的属性current,当它改变时获取对应组件并显示。

1.创建一个vue-router.js文件来写入核心代码

// 引用构造函数,VueRouter中要使用
let Vue;

// 保存选项,VueRouter类: new VueRouter({routes: [...]})
class VueRouter {

 constructor(options) {
 this.$options = options; 

} }

// 插件:实现install方法,注册$router 
// Vue构造函数,Vue.use(VueRouter)
VueRouter.install = function(_Vue) {

// 引用构造函数,VueRouter中要使用
Vue = _Vue;

// 任务1:挂载$router
// 为了能够拿到Vue根实例中的router实例
// 可以利用全局混入
Vue.mixin({
    beforeCreate() {  
    // 只有根组件拥有router选项  
    if (this.$options.router) {

    // 上下文已经是组件实例了
    Vue.prototype.$router = this.$options.router;
  } 
} });

// 任务2:实现两个全局组件router-link和router-view

Vue.component('router-link', Link)
Vue.component('router-view', View)

};
export default VueRouter;

上面代码有几个问题一起探讨下,为什么在开始定义了个变量Vue而不是引入一个真正的Vue
大家都知道,Vue.mixin、Vue.component都是Vue类的方法,Vue-Router想要使用的话就必须引入Vue,这大家都知道,但是如果把Vue打包进入Vue-router的源码内,这必然导致Vue-router的包变得很大。
为什么要用混入Vue.mixin方式写?
主要原因是use代码在前,Router实例创建在后,而install逻辑又需要用 到该实例。

2.创建router-view和router-link

VueRouter.install = function (_Vue) {
    // 省略挂载代码内容
    ...
   
  // 2.注册两个组件router-view,router-link
  // router-view就是一个容器
  Vue.component('router-view', {
    render(h) {
      // 获取路由表
      // 这里是一个vue的组件,this.$router是可以访问的
      const {routeMap, current} = this.$router
      const component = routeMap[current] ? routeMap[current].component : null
      return h(component)
    }
  })

  // <router-link to="/">xxx</router-link>
  Vue.component('router-link', {
    props: {
      to: {
        type: String,
        default: ''
      },
    },
    // h是createElement
    render(h) {
      // 参数1tag类型
      // 参数2传入各种属性和事件
      return h('a', { attrs: { href: '#' + this.to } }, this.$slots.default)
      // 也可以使用jsx
      // return <a href={'#' + this.to}>{this.$slots.default}</a>
    }
  })
}

Vue.component是vue创建组件的一种方式,render中的h函数实际就是
createElement方法,vue同样也是支持jsx语法,但是在vue中并不建议这么使用。routeMapcurrent实际实在VueRouter类初始化时候定义的下面就来实现一下。
3.处理路由表

class VueRouter {
 constructor(options) { 
    // 缓存path和route映射关系  
    this.routeMap = {} 
    this.$options.routes.forEach(route => {
     this.routeMap[route.path] = route
     }); 
} }

写成key,value模式方便使用。
image.png
4.监听url变化
当我们路由变更时候为什么页面会刷新了,首先我们要能够监听到路由的变化,hash模式下hashchange可以监听到路由变化,然后变化之后我们要响应式的通知页面变更。

class VueRouter {
  constructor(options) {
    // 创建current保存当前url
    // 为了让使用current的组件重新渲染
    // 他应该是响应式的
    const initial = window.location.hash.slice(1) || '/'
    Vue.util.defineReactive(this, 'current', initial)

    // 监听hashchange时间
    window.addEventListener('hashchange', this.onHashChange.bind(this))
    window.addEventListener('load', this.onHashChange.bind(this))
  }

  onHashChange() {
    // 修改当前url, hash的格式#/xxx
    this.current = window.location.hash.slice(1)
  }
}

Vue.util.defineReactive定义一个响应式属性,一旦current变化,router-view组件就会重新渲染。

5.引用变更
引用的使用是一样的,只是这里引入的vue-router是自己刚才创建的文件夹。

import Vue from 'vue'
import VueRouter from './vue-router'
import Home from './Home.vue'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/about',
    name: 'About',
    component: () => import(/* webpackChunkName: "about" */ './About.vue')
  }
]

const router = new VueRouter({
  routes
})

export default router

QQ20200720-162458-HD.gif
至此,vue-router基本功能都实现了,希望看完这篇文章,大家能够对vue-router如何监听页面变化,如何更新页面有了深层的了解。


西若枫
20 声望5 粉丝