详解Vue.component和Vue.extend

写在前面

众所周知,Vue 的灵魂在于: 数据驱动视图 + 组件化,本篇将为大家讲解 Vue组件 的创建,还有与其息息相关的 Vue.extend 方法。

core/global-api/assets.js

在这个文件中定义了 Vue.component 方法,我修改了部分源码便于理解,但准确度不变:

Vue.component = function(id, definition) {
  if (!definition) {
    return this.options.components[id]
  } else {
    if (isPlainObject(definition)) {
      definition.name = definition.name || id
      definition = this.options._base.extend(definition)
    }
    this.options.components[id] = definition
    return definition
  }
}

简单解读一下就是:

如果 definition 不存在,说明此前被全局注册过,那就去 Vue.options.components 中找到对应的组件返回;

如果 definition 存在,说明这是一个新的全局组件,需要被全局注册。先判断 definition 是否是一个对象,如果是则将 definition 创建为继承自 Vue构造函数 的子类实例,如果不是则说明是一个已经继承自子类的实例,将其放置到 Vue.options.components 中,然后返回。

有一些绕,那我们就通过代码来进行说明:

// 方式1
const childComponent = Vue.extend({
  data: {},
  created() {},
  methods: {},
})
Vue.component('child', childComponent)

// 方式2
Vue.component('child', {
  name: 'child-component',
  data: {},
  created() {},
  methods: {},
})

如果是方式1,那么这时候不会进入 isPlainObject(definition) 的判断,直接将 childComponent 放置到 this.options.components[‘child’] 中,然后将 childComponent 返回。

如果是方式2,那么这时候会进入 isPlainObject(definition) 的判断,当前 definition.name 有值且为 ‘child-component’,definition 会继续走入 this.options._base.extend(definition),这个方法其实就是 Vue.extend,然后将继承自 Vue构造函数 的子类的实例返回给 definition,再将这个 definition 放置到 this.options.components[‘child-component’] 中,最后将 definition 返回。

通过这两种方式我们可以知道,通过 Vue.component(id, definition) 注册的组件都会经过一层 Vue.extend(definition) 的包装生成一个子类实例,我们取名为 componentVM ,然后将这个 componentVM 放入 Vue.options.components 中,最后返回 componentVM。

你如果不明白 this.options.components 和 this.options._base.extend 是哪儿来的,请看上一章 Vue构造函数的创建过程 -> core/global-api/index.js 这部分。

core/global-api/extend.js

从 Vue.component 可以得知,组件化的核心其实是在于 Vue.extend,通过 Vue.extend 包装过后的实例才是真正的组件,那么它其中的原理又是什么呢?我将源码又修改了一下,但准确度不变:

Vue.extend = function (extendOptions) {
  extendOptions = extendOptions || {}
  const Super = this
  const SuperId = Super.cid
  const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
  if (cachedCtors[SuperId]) return cachedCtors[SuperId]
  const name = extendOptions.name || Super.options.name
  const Sub = function VueComponent (options) {
    this._init(options)
  }
  Sub.prototype = Object.create(Super.prototype)
  Sub.prototype.constructor = Sub
  Sub.cid = cid++
  Sub.options = mergeOptions(
    Super.options,
    extendOptions
  )
  Sub['super'] = Super
  if (Sub.options.props) {
    initProps(Sub)
  }
  if (Sub.options.computed) {
    initComputed(Sub)
  }
  Sub.extend = Super.extend
  Sub.mixin = Super.mixin
  Sub.use = Super.use
  ASSET_TYPES.forEach(function (type) {
    Sub[type] = Super[type]
  })
  if (name) {
    Sub.options.components[name] = Sub
  }
  Sub.superOptions = Super.options
  Sub.extendOptions = extendOptions
  Sub.sealedOptions = extend({}, Sub.options)
  cachedCtors[SuperId] = Sub
  return Sub
}

从 Vue.extend 的写法来看,很明显这是 Vue构造函数 的静态方法,所以内部的 this 指向了 Vue构造函数本身,也就是函数体内定义的 Super。

源码简单解读一下就是:

新建一个 VueComponent构造函数 命名为 Sub,通过将 VueComponent 的原型指向 Vue构造函数 的原型的方式继承 Vue构造函数 原型上所有的属性和方法,再将 Vue.use / Vue.mixin / Vue.extend / Vue.component / Vue.directive / Vue.filter 方法定义给它,让 VueComponent 也拥有这些静态方法。

接着检查传入的 extendOptions 是否拥有 props 和 computed 属性,如果有就进行初始化。如果检测出传入的 extendOptions 中含有 name 属性,则将其自动放入 VueComponent 的全局组件中 ( 不要和 Vue 的全局组件混淆 )。

然后将 Vue.options 保存到 VueComponent.superOptions 属性中,将传入的 extendOptions 保存到 VueComponent.extendOptions 属性中,并将传入的 extendOptions 封存一份保存到 VueComponent.sealedOptions 中。

最后将 VueComponent 返回,这就是继承了 Vue构造函数 的 Vue组件 的构造函数。

VueComponent = {
  cid,
  super: Vue,
  options: {
    _base: Vue,
    components: {
      KeepAlive,
      Transition,
      TransitionGroup,
    }
    directives: {
      'model',
      'show',
    }
    filters: {},
    _Ctor: {},
    ...extendOptions
  },
  use() {},
  mixin() {},
  extend() {},
  component() {},
  directive() {},
  filter() {},
  superOptions,
  extendOptions,
  sealedOptions,
  prototype: {
    _init() {},
    $data: {},
    $props: {},
    $set() {},
    $del() {},
    $watch() {},
    $on() {},
    $once() {},
    $off() {},
    $emit() {},
    _update() {},
    $forceUpdate() {},
    $destroy() {},
    $nextTick() {},
    _render() {},
    _o() {},
    _n() {},
    _s() {},
    _l() {},
    _t() {},
    _q() {},
    _i() {},
    _m() {},
    _f() {},
    _k() {},
    _b() {},
    _v() {},
    _e() {},
    _u() {},
    _g() {},
    $isServer: false,
    $ssrContext: {},
    __patch__() {},
    $mount() {},
  }
}

_isComponent

我们细心些可以注意到在 VueComponent构造函数 内调用了 this._init(options),这串代码好像有些似曾相识?没错,在 Vue构造函数 定义的时候也调用过,为了让 new Vue(options) 后自动开启初始化工作,这里也是同理。但是除了我们直接使用 Vue.extend 挂载一个组件时会去 new + $mount 一下,在它内部我们似乎没有看到哪儿使用过 new VueComponent(options),这是怎么回事呢?

而且最重要的是,我们在 Vue.prototype._init 中曾经判断过 _isComponent 这个属性,怎么既没有在 Vue.component 的定义中看到也没有在 Vue.extend 的定义中看到呢?

这时候就要牵扯到父子组件的生命周期了。具体内容会在下一篇揭晓,这里就先说个初始化生命周期过程,大家可以猜一猜是在什么时候有了 _isComponent,又是什么时候进行了 new VueComponent(options):

父组件 beforeCreate -> 父组件 created -> 父组件 beforeMount -> 子组件 beforeCreate -> 子组件 created -> 子组件 beforeMount -> 子组件 mounted -> 父组件 mounted

你可能感兴趣的:(不当切图仔,vue.js,javascript,node.js)