[关闭]
@wy 2020-04-16T13:37:31.000000Z 字数 3459 阅读 491

安装插件

源码解析


作为插件

先来回顾下使用 vue-router 的简单过程。

安装:

npm i vue-router --save-dev

使用:

  1. import Vue form 'vue'
  2. import VueRouter from 'vue-router'
  3. Vue.use(VueRouter);
  4. const router = new VueRouter({} /*路由配置信息*/)
  5. new Vue({
  6. el: '#app',
  7. router: router,
  8. })

三步即可完成路由功能的使用:

  1. 引入,作为 Vue 的插件
  2. 初始化路由对象,填写路由配置信息
  3. 将路由对象注入到 Vue 根实例中

沿着这个步骤,来拆解每一步做的事情,并触及到源码分析执行过程。

作为插件过程

根据 Vue 的插件机制,给 vue.use() 传对象,由此推断导出对象上,必然会提供一个 install 方法,打开 index.js,简化后代码:

  1. import { install } from './install'
  2. export default class VueRouter {}
  3. VueRouter.install = install
  4. if (inBrowser && window.Vue) {
  5. window.Vue.use(VueRouter)
  6. }

导出 VueRouter 类,并挂载 install 方法,提供给 vue.use() 内部调用。

还可以看出,如果在浏览器环境直接引入 vue-router 文件,则不需要手动调用 vue.use(),内部会自动完成。

分析每段代码逻辑

将目光转向 install.js 文件,完整看这里 https://github.com/vuejs/vue-router/blob/dev/src/install.js

下面对代码进行拆分,解释每段代码的作用和逻辑。

引入组件、导出函数

  1. import View from './components/view'
  2. import Link from './components/link'
  3. export let _Vue
  4. export function install (Vue) {/*代码省略,下面会一段一段分析*/}

引入两个组件,会注册成全局组件,提供在模板中使用。每个组件内部实现,稍后分析。

定义变量 _Vue,存储当前使用的 Vue 构造函数。

导出 install 函数,在 index.js 引入时使用。

不重复添加插件

  1. if (install.installed && _Vue === Vue) return
  2. install.installed = true
  3. _Vue = Vue
  4. // 判断是否不为 undefined
  5. const isDef = v => v !== undefined

设置开关,避免重复多次给同一个 Vue 添加插件。

混合功能

  1. // 此函数,目的是,找到 router-view 组件,把路径对应的组件实例,挂在路由信息的 matched 对象上
  2. const registerInstance = (vm, callVal) => {
  3. let i = vm.$options._parentVnode
  4. if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) {
  5. i(vm, callVal)
  6. }
  7. }
  8. Vue.mixin({
  9. beforeCreate () {
  10. if (isDef(this.$options.router)) {
  11. this._routerRoot = this
  12. this._router = this.$options.router
  13. this._router.init(this)
  14. Vue.util.defineReactive(this, '_route', this._router.history.current)
  15. } else {
  16. this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
  17. }
  18. registerInstance(this, this)
  19. },
  20. destroyed () {
  21. registerInstance(this)
  22. }
  23. })

利用 Vue.mixin() 向全局混合功能,意味着每一个组件都要调用混合的函数,提供的 beforeCreate 生命周期钩子函数,会在渲染组件时主动调用。

组件在渲染时,判断此组件上是否有 router 这个值,还记的最开始写的代码:

  1. new Vue({
  2. el: '#app',
  3. router: router,
  4. })

if 条件判断区分是否是根实例:

  1. 是根实例
    • 添加属性 _routerRoot 存根实例;
    • 添加属性 _router,存路由对象;
    • 调用路由对象上的 init 方法,启动路由的一系列功能,这个过程到后面再分析。
    • 添加属性 _route,获取到当前访问的路由信息
  2. 不是根实例,是其他组件实例
    • 则给每一个组件实例添加 _routerRoot,值是根实例。这样做的好处可快速找到根实例拿到路由对象。

缓存组件实例

registerInstance 函数会在 beforeCreate 调用一次,目的是缓存路径对应的组件实例;也会在 destroyed 调用一次,在切换路由卸载组件时,清除缓存的组件实例。

if 判断中可以看出来,找到父级中存在 registerRouteInstance 函数,找的就是 router-view 组件。
在定义 router-view 组件时,会在组件数据上添加这个函数。目光暂时转一下,来到 components/view.js 文件中,找到这段代码:

  1. //添加实例
  2. //将在注入的生命周期挂钩中调用
  3. data.registerRouteInstance = (vm, val) => {
  4. const current = matched.instances[name]
  5. if (
  6. (val && current !== vm) ||
  7. (!val && current === vm)
  8. ) {
  9. matched.instances[name] = val
  10. }
  11. }

matched 是当前匹配到的路由信息对象,把组件实例缓存到这个对象的 instances 中。
val 有值时,添加上;没有值时,清除掉。

添加 $router 和 $route

回想在组件中调用 this.$router 得到路由对象,this.$route 得到当前访问的路由信息。拿到的是挂载在根组件上的属性,有代码为证:

  1. Object.defineProperty(Vue.prototype, '$router', {
  2. get () { return this._routerRoot._router }
  3. })
  4. Object.defineProperty(Vue.prototype, '$route', {
  5. get () { return this._routerRoot._route }
  6. })

Vue 原型上添加属性 $router$route,所有的组件实例都继承 Vue.prototype,自然能够找到这两个属性。

这里处理的很巧妙,当访问时,会触发访问器 get 函数,返回当前组件实例上存的根实例上添加的 _routerroute 属性。(不明白的可以回看,上面beforeCreated中的逻辑)

注册全局组件

  1. Vue.component('RouterView', View)
  2. Vue.component('RouterLink', Link)

注册全局组件,将来在组件模板中使用。

路由钩子函数合并策略

  1. const strats = Vue.config.optionMergeStrategies
  2. strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created

组件选项中可以写路由提供的钩子函数,使用 Vue.mixin() 混合功能时,这些钩子函数的合并规则,和内置的生命周期函数 created 的合并策略保持一致。策略很简单,就是不进行覆盖,而是两个钩子函数先后执行。

来段代码:

  1. // 要混合的功能
  2. let feature = {
  3. beforeRouteEnter(){
  4. console.log('我是混合功能, beforeRouteEnter')
  5. },
  6. created(){
  7. console.log('我是混合功能, created')
  8. }
  9. }
  10. Vue.extend({
  11. mixin:[feature],
  12. beforeRouteEnter(){
  13. console.log('beforeRouteEnter')
  14. },
  15. created(){
  16. console.log('created')
  17. }
  18. })

这段代码给组件混合了两个钩子函数,都会被执行,而不是组件的钩子函数覆盖混合过来的函数。这就是上面设置了合并策略的效果。

总结:

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注