vue-router简易源码解析(3-1-1)

Charon

最近了解了一下vue,然后简单了解了一下vue-router,然后现在简单得写一个vue-router得简易版得源码解析.

这是vue-router得基本用法 vue-router基本用法

vue的生命周期官网给出的好棒
https://cn.vuejs.org/images/l... 安利一下
首先我们要了解:插件,slot插槽,混入,render函数,运行时和完整版得vue

首先我们用vue-cli创建一个项目,要使用带编译器得版本,因为要编译模板。
然后我们找到对应得router/index.js,把官方得vue-router,替换成我们自己新建得

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

路由界面配置

const routes = [
  {
    path: '/',
    name: 'Index',
    component: Index
  },
  {
    path: '/login',
    name: 'Login',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import(/* webpackChunkName: "about" */ '../views/Login.vue')
  },
  {
    path: '/detail/:id',
    name: 'Detail',
    props: true,
    component: () => import(/* webpackChunkName: "detail" */ '../views/Detail.vue')
  }
]

const router = new VueRouter({
  routes
})

app.vue界面我们简单设置一下
image.png

然后开始考虑Vue.use(VueRouter) 注册插件内部会调用传入对象得install方法

这一节得主要核心在vue-router文件上
主要得实现思路就是:

创建VueRouter类添加静态方法
  • 判断插件是否加载了,不重复加载
  • 记录vue得构造函数
  • 当Vue加载得时候把传入得router对象挂载到vue原型上(只执行一次,使用混入的方式)

创建VueRouter里的具体方法

  • 初始化,options,routeMap,data(当前路由,响应式数据)
  • createRouteMap把路由信息生成一个映射放到routeMap中
  • initComponent创建路由所使用得组件router-link,router-view
  • initEvent监听浏览器的事件hash或history

下面是具体实现

let _Vue = null

class VueRouter {
  static install (Vue) {
    // 1 判断当前插件是否被安装
    if (VueRouter.install.installed) {
      return
    }
    VueRouter.install.installed = true // 已安装
    // 2 把Vue的构造函数记录在全局
    _Vue = Vue

    // 3 把最开始创建Vue的实例时传入的router对象,也就是实例化得VueRouter对象,
    // vue在实例化的时候会把router放到$options上,我们取到它混入创建之前生命周期注入到Vue原型上 方便
    // 所有得vue实例内部 使用 this.$router都能访问到
    _Vue.mixin({
      beforeCreate () {
        if (this.$options.router) {
          console.dir(this)
          _Vue.prototype.$router = this.$options.router
        }
      }
    })
  }

  constructor (options) {
    this.options = options // 把实例化路由时得参数储存起来
    this.routeMap = {} // 路由对应得映射,component
    this.historyMode = options.mode === 'history'
    this.data = _Vue.observable({ // 创建一个observable被观察者,响应式数据
      current: '/'
    })
    this.init() // 初始化路由信息
  }

  init () {
    this.createRouteMap()// 创建映射信息
    this.initComponent(_Vue)// 创建使用得组件router-link router-view
    this.initEvent()// 监听浏览器 点击左右历史popstate事件
  }

  createRouteMap () { // 遍历所有的路由规则 吧路由规则解析成键值对的形式存储到routeMap中
    this.options.routes.forEach(route => {
      this.routeMap[route.path] = route.component // 值就是对应得组件
    })
  }

  initComponent (Vue) { // 创建路由组件
    const that = this
    const routerLinkOption = {
      props: {
        to: String // 设置router-link得props to 跳转地址 必须是字符串
      },
      render (h) { // 设置组件得render函数,不了解vue render何时执行得可以看下它的生命周期
        const on = {}; let href = `#${this.to}`
        if (that.historyMode) {
          on.click = this.clickhander
          href = this.to
        }
        return h('a', { // h函数生成虚拟dom
          attrs: { // 设置属性
            href
          },
          on// 事件
        }, [this.$slots.default]) // 最后是组件里的子元素,我们通过插槽获取 到
      }
    }
    if (that.historyMode) {
      routerLinkOption.methods = {
        clickhander (e) { // 组件得方法
          e.preventDefault() // 阻止默认跳转
          history.pushState({}, '', this.to) // history模式改变url地址,不发送请求
          this.$router.data.current = this.to// 改变observable对象得值,触发响应式观察者更新
        }
      }
    }

    Vue.component('router-link', routerLinkOption)

    Vue.component('router-view', { // 视图展示组件
      render (h) {
        console.log(that.data.current)
        const cm = that.routeMap[that.data.current] // 取到路由对应得compoment
        return cm ? h(cm) : h('div', '404') // 传给h函数,会帮我们转换为虚拟dom
      }
    })
  }

  initEvent () { // 初始化浏览器前进后退事件拦截
    let loadCb = null
    if (this.historyMode) {
      this.historyInitEvent()
      loadCb = this.historyChangeHandler
    } else {
      this.hashInitEvent()
      loadCb = this.hashChangeHandler
    }
    window.addEventListener('load', loadCb)
  }

  historyInitEvent () {
    window.addEventListener('popstate', this.historyChangeHandler)
  }

  historyChangeHandler=() => { // 拦截,要箭头函数使用this
    this.data.current = window.location.pathname // 改变observable对象,触发router-view更新
  }

  hashChangeHandler=() => {
    this.data.current = window.location.hash.substr(1) || '/'
  }

  hashInitEvent () {
    window.addEventListener('hashchange', this.hashChangeHandler)
  }
}
export default VueRouter

主要的核心代码都在这个vue-router文件里了,这只是个简易版,简单模仿了一下,功能肯定有很多不足,知识了解下原理,有兴趣的可以拿下来,替换原始的vue-router跑一下,看看效果。
该内容借鉴于拉钩大前端训练营

阅读 397

世界核平

34 声望
11 粉丝
0 条评论
你知道吗?

世界核平

34 声望
11 粉丝
宣传栏