手写简易 VueRouter插件

612 阅读2分钟

手写简易 VueRouter插件

本文用于复盘自己平时的学习成果。本文针对于有一定 Vue 基础的同学。本文会手写一个简易的 VueRouter 插件,实现路由的基本跳转。本文的讲解思路:

  1. 简单分析原 VueRouter 使用,提出几点疑问。
  2. 列出简易 VueRouter 的关键点
  3. 实现简易 VueRouter 插件。

一、原 VueRouter 插件分析

VueRouter 的使用

router.js 路由配置文件中

// 第一步 引入 VueRouter 插件
import VueRouter fromVueRouter// 第二步 安装 VueRouter 插件
Vue.use(VueRouter)
// 第三步 定义路由
const routes = [...]
// 第四步 创建路由实例
const router = new VueRouter({routes})

main.js 中

// 引入路由 router
import router from './router'

// 挂载 router 到 Vue 实例上
new Vue({
  router,
  render: h => h(App)
}).$mount('#app')

添加路由视图 App.vue

<template>
  <div id="app">
    <div id="nav">
      <!-- 路由跳转连接 -->
      <router-link to="/">Home</router-link> |
      <router-link to="/about">About</router-link>
    </div>
    <router-view></router-view>
  </div>
</template>
// 路由跳转
this.$router.push('/')
this.$router.push('/about')

分析:

  1. 为什么我们要 Vue.use(VueRouter) ?

    • 安装 Vue 插件,如果插件是一个对象,必须提供 install 方法。如果插件是一个函数,它会被作为 install 方法。install 方法调用时,会将 Vue 作为参数传入。

      该方法需要在调用 new Vue() 之前被调用。

      当 install 方法被同一个插件多次调用,插件将只会被安装一次。

  2. new VueRouter() 我们需要传 routes 等参数作为配置项

    • 说明我们在实现简易 VueRouter 插件是需要传入 options 作为配置项
  3. 为什么要把 router 挂到 Vue 实例上?

    • 在根上添加 router,以便在全局访问 this.$router 进行路由的相关操作
  4. 添加路由视图时,使用了 组件,为什么可以直接使用这两个组件?

    • 说明全局注册了这两个组件

二、实现简易 VueRouter 需求分析

明确简易 VueRouter 需要实现哪些功能:

typora.jpg

三、代码实现

第一步 按照上面思路搭一个框架

let Vue
// 1. 定义一个类
class VueRouter {
  // 2. 我们使用时是 new VueRouter({routes}),构造函数传入相应的路由配置
  constructor(options) {
    this.$options = options
    // 3. 获取当前路由,以便后续根据路由渲染对应的模板
    // 获取路由地址,比如我们路由地址 http://localhost:8080/#about,实际上我们只需要about就行了
    const initial = window.location.hash.slice(1) || '/'
    // 监听 hash 变化来获取当前路由地址
    window.addEventListener('hashchange', () => [
      this.current = window.location.hash.slice(1)
    ])
  }
}
// 3.Vue.js 的插件应该暴露一个 install 方法。这个方法的第一个参数是 Vue 构造器,
VueRouter.install = (_Vue) => {
  Vue = _Vue
  // 4. 全局注册 router-link、router-view 组件
  Vue.component('router-link', {})
  Vue.component('router-view', {})
}

export default VueRouter

基本的框架就搭好了,但是对比我们的思路,除了细节实现的填充,是不是还少了一步,把 $router 挂到跟上,以便再任何地方都可以 this.$router访问。

因为 new VueRouter({router}),我们怎么拿 router 呢?肯定是从传进来的 options里拿 router 。

我们可以试一下

VueRouter.install = (_Vue) => {
  Vue = _Vue
 	// 新增
  console.log(’this‘, this)
  Vue.prototype.$router = this.$options.router
  
  Vue.component('router-link', {})
  Vue.component('router-view', {})
}

我们跑一跑看

typora.jpg

发现 this 是 undefined,这是为什么呢?我们从上文 VueRouter 的使用中可以看到。

Vue.use(VueRouter)
const router = new VueRouter({routes})
// 引入路由 router
import router from './router'

// !!!是不是我们安装是在 new Vue 之前,也就是说安装VueRouter.install = (_Vue) =>{} 这个时候,Vue 的实例还没创建,_Vue自然就是 undefined 了
new Vue({
  router,
  render: h => h(App)
}).$mount('#app')

那么我们怎么解决这个问题呢?我们可以利用 Vue.mixin 全局混入,等到 Vue 实例创建后再去添加 $router

Vue.mixin({
    beforeCreate() {
      // 因为是全局混入,所以每个组件创建时都会调用,但是只有根实例创建时才传入 router 选项,所以添加如下判断哪避免多次执行
      if (this.$options.router) {
        Vue.prototype.$router = this.$options.router
      }
    }
  })

第二步填充细节

let Vue
class VueRouter {
  constructor(options) {
    this.$options = options
    // 获取路由地址,比如我们路由地址 http://localhost:8080/#about,实际上我们只需要about就行了
    const initial = window.location.hash.slice(1) || '/'
    console.log(initial)
    // 监听 hash 变化来获取当前路由地址
    window.addEventListener('hashchange', () => [
      this.current = window.location.hash.slice(1)
    ])
  }
}
// Vue.js 的插件应该暴露一个 install 方法。这个方法的第一个参数是 Vue 构造器,
VueRouter.install = (_Vue) => {
  Vue = _Vue
  console.log('this', this)
  Vue.mixin({
    beforeCreate() {
      // 因为是全局混入,所以每个组件创建时都会调用,但是只有根实例创建时才传入 router 选项,所以添加如下判断哪避免多次执行
      if (this.$options.router) {
        Vue.prototype.$router = this.$options.router
      }
    }
  })
  
  Vue.component('router-link', {
    props: {
      to: {
        type: String,
        required: true
      }
    },
    render(h) {
      return h(
        'a',
        {
          attrs: {
            href: '#' + this.to
          }
        },
        this.$slots.default
      )
    }
  })
  Vue.component('router-view', {
    render(h) {
      let componnent = null
      const route = this.$router.$options.routes.find(
        (route) => route.path === this.$router.current
      )
      if (route) {
        componnent = route.componnent
      }
      return h(componnent)
    }
  })
}

export default VueRouter

我们在运行看看,我们发现点击导航路由地址变了,但是并没有重新渲染视图,我们想要每次路有变化都重新渲染视图,我们会想到响应式数据每次发生变化都会调用render渲染函数更新视图。所以我们还需补充,把当前路由设置为相应数据。我们能使用 Vue.util.defineReactive

class VueRouter {
  constructor(options) {
    this.$options = options
    // 获取路由地址,比如我们路由地址 http://localhost:8080/#about,实际上我们只需要about就行了
    const initial = window.location.hash.slice(1) || '/'
    // !!!定义一个对象的响应属性
    Vue.util.defineReactive(this, 'current', initial)
    // 监听 hash 变化来获取当前路由地址
    window.addEventListener('hashchange', () => {
      this.current = window.location.hash.slice(1)
    })
  }
}

这样下来就实现了一个简易的 VueRouter 插件了。今天就记录这么多吧,哈哈哈!

参考:开课吧杨老师课堂

Vue官方文档