Vue-router的简单实现

651 阅读4分钟

最近看了一些教学,作为自己的总结一下。实现一下low 版本Vue-router

第一步,分析

  1. vue-router是作为插件来使用的。所以需要暴露一个install方法,详情参考这里
  2. 在我们平常使用过程中,需要就收参数,是一个对象。所以类的constructor需要接收一个对象
  3. Vue-router里面有两个方法,router-link,router-view,需要全局注册
  4. 分析一下他总共做的事情,
    1. 监听路由的变化
    2. 改变对应的模板渲染
    3. 实现router-link,router-view这两个方法

第二步,开始搭建框架

  1. 首先我先创建一个叫MyVueRouter的类,里面的构造方法里面有一个叫options的参数。还有对应的三个方法,分别执行 第一步4中的事情,还是实现install方法

MyVueRouter类

let Vue // 先定义定义一个Vue
class MyVueRouter {
  constructor (options) { // 在创建实例的时候会接受一个配置项
    this.$options = options
      this.routeMap = {} // 后面会说

    // 路由响应式 强绑定,这就是与 react 里面router区别的原因 只能用于Vue
    this.app = new Vue({
      data: {
        current: '/'
      }
    })
  }

  init () {
    this.bindEvents() // 监听 url变化,绑定事件
    this.createRouterMap(this.$options) // 解析路由配置
    this.initComponent() // 实现router-view 那两个组件
  }

  bindEvents () { // 监听 url变化
    
  }

  onHashChange () { // 解析路由配置
    
  }

  createRouterMap (options) { // 实现router-view 那两个组件
    
  }

  initComponent () {
    // router-link router-view
    Vue.component('router-link', {
      
      }
    })

    // router-view
    Vue.component('router-view', {
        
    }

}

说一下上面为什么要new Vue(),我们需要一个值来 记录当前的地址栏current,所以这里面就用到了Vue的特性。这就是Vue-router只能用在Vue不能用在其他语言中的原因

install 方法

// 作为创建
MyVueRouter.install = function (_vue) { // 作为插件 实际执行的是这个方法   在执行过程中会返回一个Vue实例
    Vue = _vue
  Vue.mixin({ // 扩展组件
    beforeCreate () {
      // this 是当前Vue实例
      if (this.$options.router) {
        // 在跟组件执行一次
        Vue.prototype.$router = this.$options.router
        this.$options.router.init()
      }
    }
  })
}
// 使用组件
Vue.use(MyVueRouter)

第三步 具体方法的实现

注意这里只实现了hash路由

bindEvents方法

首先理清楚这个方法的作用就是,监听url的变化。当url变化的时候执行指定的方法/函数

bindEvents () {
    // 监听刚打开页面的时候 注意绑定this指向
    window.addEventListener('load', this.onHashChange.bind(this))
     // 监听刚url变化的时候
    window.addEventListener('hashchange', this.onHashChange.bind(this))
  }

常用事件的网址,需要的大家自行点击 这里 ,注意 不需要加on

onHashChange方法

onHashChange () {
    this.app.current = window.location.hash.slice(1) || '/'
    // 方便大家看到所有改变 打印的日志
    console.log(this.app.current)
    console.log(window.location.hash)
  }

这个没什么解释的,就是利用改变Vue的响应, 改变current

为什么要从1位开始?

hash路由,前面有# 详细请看 打印日志

createRouterMap

作用 解析路由配置,让其一一对应

这时在上面创建的routeMap就有用了

createRouterMap (options) {
    options.routes.forEach(item => {
      this.routeMap[item.path] = item.component
    })
  }

创建键值对,使component和路由current一一对应

initComponent方法

作用 实现router-link,router-view这两个方法

initComponent () {
    // router-link router-view
    Vue.component('router-link', {
      props: { to: String },
      render (createElement) {
        // createElement 参数 tag data(可选) children attrs原生的html属性  this.$slots.default a标签里面的文字
        return createElement('a', { attrs: { href: '#' + this.to } }, [this.$slots.default]) 
        // 返回Vnode 虚拟dom
      }
    })

    // router-view
    Vue.component('router-view', {
      render: (h) => { // h createElement简写  用件头函数 改变了this指向
        // 全局组件 跟全局vue有关 里面属性发生变化
        const comp = this.routeMap[this.app.current]
        // comp 拿到了对应的组件
        return h(comp)
      }
    })
  }

router-link

有关 createElement的详情,请点击这里

有关render 函数,请点击 这里

首先进行全局注册

接受一个参数 props:{to: String}

平时使用的是这样使用,如果有需要多加一些,这里只实现这一个

<router-link to="/">Home</router-link>

因为 router-link最主要作用是跳转,所以我们创建一个a标签,配置跳转信息来实现跳转

router-view

注意,这里面使用的是箭头函数,不是普通函数,目的,解决this指向问题

因为使用的过程中不需要参数,所以不用写props

我们只需要拿到当前current对应的,拿到对应的模板,交给createElement渲染就行

第四步 完整代码

import a from './../components/a'
import b from './../components/b'

// url变化  路由解析  组件配置
class MyVueRouter {
  constructor (options) { // 在创建实例的时候会接受一个配置项
    this.$options = options
    this.routeMap = {}
    // 路由响应式 强绑定,这就是与 react 里面router区别的原因 只能用于Vue
    this.app = new Vue({
      data: {
        current: '/form'
      }
    })
  }

  init () {
    this.bindEvents() // 监听 url变化
    this.createRouterMap(this.$options) // 解析路由配置
    this.initComponent() // 实现router-view 那两个组件
  }

  bindEvents () {
    window.addEventListener('load', this.onHashChange.bind(this))
    window.addEventListener('hashchange', this.onHashChange.bind(this))
  }

  onHashChange () {
    this.app.current = window.location.hash.slice(1) || '/'
    console.log(this.app.current)
    console.log(window.location.hash)
  }

  createRouterMap (options) {
    options.routes.forEach(item => {
      this.routeMap[item.path] = item.component
    })
  }

  initComponent () {
    // router-link router-view
    Vue.component('router-link', {
      props: { to: String },
      render (createElement) {
        // createElement 参数 tag data(可选) children attrs原生的html属性  this.$slots.default a标签里面的文字
        return createElement('a', { attrs: { href: '#' + this.to } }, [this.$slots.default])
      }
    })

    // router-view
    Vue.component('router-view', {
      render: (h) => {
        // h createElement缩写  用件头函数 改变了this指向  // 全局组件 跟全局vue有关 里面属性发生变化
        const comp = this.routeMap[this.app.current]
        console.log(comp)
        return h(comp)
      }
    })
  }
}

// 作为创建
MyVueRouter.install = function (Vue) {
  // 作为插件 实际执行的是这个方法
// 是可以收到一个Vue实例
  Vue.mixin({ // 扩展组件
    beforeCreate () {
      // this 是当前Vue实例
      if (this.$options.router) {
        // 在跟组件执行一次
        Vue.prototype.$router = this.$options.router
        this.$options.router.init()
      }
    }
  })
}
Vue.use(MyVueRouter)

export default new MyVueRouter({
  routes: [
    {
      path: '/',
      name: 'A',
      component: a
    },
    {
      path: '/b',
      name: 'B',
      component: b
    }
  ]
})

APP.js代码

<template>
  <div id="app">
    <router-link to="/">Home</router-link> |
    <router-link to="/b">B</router-link> |
    <router-view></router-view>
  </div>
</template>