手写一个简单的vue-router(其实很简单)

首先回顾一下我们使用vue-router的过程。

// 1. 注册路由插件(这里执行的是对应插件的install方法,所以要实现一个静态的install方法)
Vue.use(VueRouter)
// 2.编写路由规则
const routes = [
  {
    path: '/',
    name: 'Index',
    component: Index
  }
]
// 3. 创建 router 对象并且导出(由此得出VueRouter是一个类)
const router = new VueRouter({
  mode: 'hash',
  routes
})
export default router
// 4. 引入router对象,并注册 router 对象
import VueRouter from 'vue-router'
new Vue({
  router,
  render: h => h(App)
}).$mount('#app')
// 5.router-link,router-view的使用

单页面的应用始终秉承一个原则:修改访url但是不发送请求,然后视图进行更新。
首先解决第一个问题,如何让浏览器知道用户修改了url呢?
在hash模式下,主要通过hashchange的事件去监听路径的变化。然后通过window.location.hash来获取#,包括#后面的所有字符串,那么我们就知道了用户想要跳转到哪一个url了,然后根据url我们就找到了对应的组件了。在history模式下,通过popstate事件去监听用户前进,后退的操作,通过history.pushState方法去修改浏览器的url。
第二个问题:我们虽然知道用户要跳转到什么url,也知道对应的组件,那么如何让视图更新呢?
这里需要用到vue设计的原理:响应式。其实很简单理解,数据修改了,视图要更新,那么怎么更新呢,那就要做一些事情让视图更新,那这些事情其实是一系列的函数。响应式数据被修改后,会执行对应所依赖的渲染函数,这些渲染函数一般就是用来做视图更新操作的。所以我们要定义一个响应式的数据,来维护当前的路由的url以及相关的信息,当这个数据改变的时候就会执行对应的渲染函数,视图就会更新。
第三个问题:上面所说的渲染函数是什么?
我们都知道,我们对应的组件显示,是使用router-view去实现的。那么router-view其实是一个vue-router所注册的组件,当然组件就会有自己的渲染函数,所以上面所提到的响应式数据的改变然后执行对应的渲染函数,这个渲染函数就是router-view的渲染函数。

事已至此,直接上代码,每个方法都有注释。

let _Vue = null
export default class VueRouter {
  // Vue.use(VueRouter) Vue的插件机制其实是使用对应插件的install方法
  static install (Vue) {
    // 1.判断当前插件是否已经安装
    if (VueRouter.install.installed) {
      return
    }
    VueRouter.install.installed = true
    // 2.把Vue构造函数记录到全局变量(因为后面要注册router-link router-view需要用到全局的组件)
    _Vue = Vue
    // 3.把创建的router对象注入到全局对象上
    // 混入,这样使得每一个每一个vue实例的组件都会执行在beforeCreate生命周期中执行对应
    // 的方法,所以需要加一层判断,只要在根实例上面使用这个方法就好了。
    _Vue.mixin({
      beforeCreate () {
        if (this.$options.router) {
          _Vue.prototype.$router = this.$options.router
          this.$options.router.init()
        }
      }
    })
  }
  // 构造函数,options是用户所定义的参数,routerMap是路由地址到组件的映射,data是个响应式数据,记录了当前路由的信息。
  constructor(options) {
    this.options = options
    this.routerMap = {}
    this.data = _Vue.observable({
      current: '/'
    })
  }
  // 初始化路由,执行三个方法。
  // createRouterMap创建路由到图的映射
  // initComponents创建router-link,router-view组件
  // initEvent绑定对应的事件来触发页面更新
  init () {
    this.createRouterMap()
    this.initComponents(_Vue)
    this.initEvent()
  }

  createRouterMap () {
    // 遍历options(路由规则),把路由规则解析成键值对
    this.options.routes.forEach(route => {
      this.routerMap[route.path] = route.component
    })
  }

  initComponents (Vue) {
    const self = this
    // 使用Vue实例下的component方法来创建组件
    Vue.component('router-link', {
      props: {
        to: String
      },
      // 组件的渲染函数,提供了一个h参数,这个h参数其实是一个函数,用来创建虚拟dom的
      render (h) {
        // router-link渲染出来其实是a标签
        return h('a', {
          // attrs是这个dom节点的属性
          attrs: {
            // 哈希模式和history模式的判断
            href: self.options.mode == 'hash' ? `#${this.to}` : this.to
          },
          on: {
            // history模式则需要绑定点击事件
            click: self.options.mode == 'hash' ? () => { } : this.clickHandler
          }
        }, [this.$slots.default])
      },
      methods: {
        // 其实底层是使用history.pushState来修改路由(浏览器上面的地址),但是不会触发页面刷新
        // 所以我们还要手动修改data(维护了当前路由的信息),这个data是个响应式对象,修改它会自动
        // 触发所依赖的对应渲染函数,因此页面更新。
        clickHandler (e) {
          history.pushState({}, '', `${this.to}`)
          this.$router.data.current = this.to
          e.preventDefault()
        }
      }
      // template: '<a :href="to"><slot></slot></a>'
    })
    Vue.component('router-view', {
      // data所依赖的渲染函数,所以data改变了,页面才刷新。
      render (h) {
        const component = self.routerMap[self.data.current]
        return h(component)
      }
    })
  }
  // hash模式下,主要通过hashchange事件来监听,然后修改对应的data值。
  // window.location.hash指的是浏览器地址中 #包括#后面的字符串,是个
  // 可读可写的值。
  initEvent () {
    if (this.options.mode == 'hash') {
      window.addEventListener('hashchange', () => {
        this.data.current = window.location.hash.slice(1) || '/'
      })
      window.addEventListener('load', () => {
        this.data.current = window.location.hash.slice(1) || '/'
      })
    } else {
      window.addEventListener('popstate', () => {
        this.data.current = window.location.pathname
      })
    }
  }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值