简易版MyRouter的实现

956 阅读3分钟

需求

  1. 完成插件的挂载
  2. 完成两个组件
  3. 实现监听与渲染

基本逻辑流程

  1. 始化时,将vue-router中配置的router配置项挂载到vue实例上
  2. 在页面中需要的位置添加router-link 与 router-view
- router-link 会改变url
- router-view 展示组件
  1. 添加路由监听,分为为url在load和change的时候触发
  2. 当路由发生变化时,router定义的响应式属性 current 会获取到当前的url地址,并根据地址去获取组件,最后在viwe中渲染组件

实现

  1. 组件router-view的实现
export default {
  // 导出一个组件的配置项  
  // 主要功能是提供一个位置,渲染对应的组件
  // 当url发生变化时,根据当前的url,获取到需要渲染的组件,然后重新渲染
  render(h) {
    let component = null
    // current是响应式的,当它发生变化时,会去触发view组件重新执行rouder函数
    this.$router.$options.routes.forEach(route => {
      if (route.path === this.$router.current) {
        component = route.component
      }
    })
    return h(component)
  }
}
  1. 组件router-link的实现
export default {
  // 同样导出一个组件的配置项  
  // 主要功能为 进行url的跳转
  
  // 组件必须接收 to 属性,用于url的跳转,同时在此可以扩展tag/event等属性以及定义to属性可以接收obj等操作
  props: {
    to: {
      type: String,
      required: true
    }
  },

  render(h) {
    // return <a href={'#'+this.to}>{this.$slots.default}</a>;
    // h函数=》 vue内部的createElement函数  接收三个参数 标签名  属性  子组件
    return h('a', {
      attrs: {
        href: '#' + this.to
      }
    }, [
      this.$slots.default
    ])
  }
}
  1. my-router的实现
  • 目标1:在全局挂载 router-link 和 router-view 两个组件
// 实现全局挂载两个组件  引入前面实现的组件配置项,并在install方法中挂载至全局
import Link from './my-router-link'
import View from './my-router-view'

// vue的插件系统,在是用vue.use('plugin')时  会执行plugin的install方法 同时install会默认将vue传入
VueRouter.install = function(_vue) {
  vue = _vue

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

export default VueRouter
  • 目标2: 在实例创建的时候,将VueRouter创建的router配置挂载到vue根实例上
// 实现将router配置项挂载到vue实例上

let vue
VueRouter.install = function(_vue) {
  vue = _vue
  // 全局挂载$router  可以利用mixin来进行混入
  vue.mixin({
    // 需要在实例创建之后才进行混入 
    // 与一些如弹窗组件等可以在挂载在全局的vue.prototype在挂载的时间上有一点区别
    beforeCreate() {
      // 做一层判断,只有根实例才有router选项
      if (this.$options.router) {
        vue.prototype.$router = this.$options.router
      }
    }
  })
}

export default VueRouter
  • 目标3:创建VueRouter
// 实现VueRouter类 实现监听

let vue
// 任务1:实现监听
// 任务2:需要有一个响应式属性来保存当前的url
class VueRouter {
  constructor(options) {
    this.$options = options
    // 设置响应式的current,用于保存当前的url 并利用响应式去通知更新
    const initial = window.location.hash.slice(1) || '/'

    // vue提供了三种添加响应式数据的方法 
    //    vue.set   
    //    vue.observable
    //    以及内部的vue.util.defineReactive
    
    // vue.set   是在一个已经是响应式对象的基础上添加属性  
    //  官方文档说明:向响应式对象中添加一个 property,
    //  并确保这个新 property 同样是响应式的,且触发视图更新。
    
    // vue.observable  是将一个对象改变为响应式对象   
    //  官方文档说明:让一个对象可响应。Vue 内部会用它来处理 data 函数返回的对象
    
    // vue.util.defineReactive 是内部的一种方法 并没有在文档中对外暴露
    //  功能为向一个对象添加一个属性,属性的值可以是字符串等基本对象
    
    vue.util.defineReactive(this, 'current', initial)
    // const state = Vue.observable({ count: 0 })
    // vue.set(this, 'current', initial)

    // 监听hashchange事件,在load和hashchange时,均会触发更新
    window.addEventListener('hashchange', this.onHashChange.bind(this))
    window.addEventListener('load', this.onHashChange.bind(this))
  }
  
  // 将当前的url赋值给current 从而触发view的重新渲染
  onHashChange() {
    this.current = window.location.hash.slice(1)
  }
  
  // 同时可以进行mode的扩展,当mode为history时,改变监听的方式和获取url的方法
}

export default VueRouter
  • my-router完整代码
import Link from './my-router-link'
import View from './my-router-view'

let vue

class VueRouter {
  constructor(options) {
    this.$options = options
    const initial = window.location.hash.slice(1) || '/'

    vue.util.defineReactive(this, 'current', initial)

    window.addEventListener('hashchange', this.onHashChange.bind(this))
    window.addEventListener('load', this.onHashChange.bind(this))
  }
  
  onHashChange() {
    this.current = window.location.hash.slice(1)
  }
}

VueRouter.install = function(_vue) {
  vue = _vue

  vue.mixin({
    beforeCreate() {
      if (this.$options.router) {
        vue.prototype.$router = this.$options.router
      }
    }
  })
  vue.component('router-link', Link)
  vue.component('router-view', View)
}

export default VueRouter

拓展、优化

  1. 提前做好组件映射,避免在url改变时重复执行循环遍历
// 在VueRouter中
class VueRouter {
  constructor(options) {
    // 缓存path和route映射关系
    this.routeMap = {}
    this.$options.routes.forEach(route => {
    this.routeMap[route.path] = route
    });
  }
}
// 在router-view中
export default {
  render(h) {
    const {routeMap, current} = this.$router
    const component = routeMap[current] ? routeMap[current].component : null;
    return h(component);
  }
}
  1. 嵌套路由问题
  • 后续补上