Skip to content
On this page

vue-class-component ​

  • 类组件:使用类来定义 Vue 组件,可以继承其他组件或混入其他组件的功能。

  • 装饰器:使用装饰器来添加组件选项、生命周期钩子和自定义的属性。

  • Mixins:可以通过混入(Mixins)的方式,将共用的功能或逻辑复用到多个组件中。

  • TypeScript 支持:完全支持 TypeScript,可以使用 TypeScript 来编写类型安全的 Vue 组件。

Component ​

js
// 判断是否是函数调用,传参时options是对象
function Component(options: ComponentOptions<Vue> | VueClass<Vue>): any {
  if (typeof options === 'function') {
    return componentFactory(options);
  }
  return function (Component: VueClass<Vue>) {
    return componentFactory(Component, options);
  };
}

componentFactory ​

js
export const $internalHooks = [
  'data',
  'beforeCreate',
  'created',
  'beforeMount',
  'mounted',
  'beforeDestroy',
  'destroyed',
  'beforeUpdate',
  'updated',
  'activated',
  'deactivated',
  'render',
  'errorCaptured', // 2.5
  'serverPrefetch' // 2.6
]

export function componentFactory (
  Component: VueClass<Vue>,
  options: ComponentOptions<Vue> = {}
): VueClass<Vue> {
  options.name = options.name || (Component as any)._componentTag || (Component as any).name
  // prototype props.
  const proto = Component.prototype
  // 遍历所有属性
  Object.getOwnPropertyNames(proto).forEach(function (key) {
    if (key === 'constructor') {
      return
    }

    // hooks 是生命周期或者特定钩子就返回
    if ($internalHooks.indexOf(key) > -1) {
      options[key] = proto[key]
      return
    }
    // 获取函数描述属性
    const descriptor = Object.getOwnPropertyDescriptor(proto, key)!
    if (descriptor.value !== void 0) {
      // methods 函数就加到方法里面
      if (typeof descriptor.value === 'function') {
        (options.methods || (options.methods = {}))[key] = descriptor.value
      } else {
        // typescript decorated data 不是函数就加入到data里面
        (options.mixins || (options.mixins = [])).push({
          data (this: Vue) {
            return { [key]: descriptor.value }
          }
        })
      }
    } else if (descriptor.get || descriptor.set) {
      // computed properties get、set添加到计算属性
      (options.computed || (options.computed = {}))[key] = {
        get: descriptor.get,
        set: descriptor.set
      }
    }
  })

  // add data hook to collect class properties as Vue instance's data
  ;(options.mixins || (options.mixins = [])).push({
    data (this: Vue) {
      return collectDataFromConstructor(this, Component)
    }
  })

  // decorate options 执行所有绑定到__decorators__的装饰器函数
  const decorators = (Component as DecoratedClass).__decorators__
  if (decorators) {
    decorators.forEach(fn => fn(options))
    delete (Component as DecoratedClass).__decorators__
  }

  // find super 查到到他的父类,调用父类extend方法
  const superProto = Object.getPrototypeOf(Component.prototype)
  const Super = superProto instanceof Vue
    ? superProto.constructor as VueClass<Vue>
    : Vue
  const Extended = Super.extend(options)

  forwardStaticMembers(Extended, Component, Super)

  if (reflectionIsSupported()) {
    copyReflectionMetadata(Extended, Component)
  }

  // 返回Extended
  return Extended
}

forwardStaticMembers ​

js
// 需要忽视的vue属性
const reservedPropertyNames = [
  // Unique id
  'cid',

  // Super Vue constructor
  'super',

  // Component options that will be used by the component
  'options',
  'superOptions',
  'extendOptions',
  'sealedOptions',

  // Private assets
  'component',
  'directive',
  'filter'
]

// 需要忽视的浏览器属性
const shouldIgnore = {
  prototype: true,
  arguments: true,
  callee: true,
  caller: true
}

function forwardStaticMembers (
  Extended: typeof Vue,
  Original: typeof Vue,
  Super: typeof Vue
): void {
  // We have to use getOwnPropertyNames since Babel registers methods as non-enumerable
  // 获取传入对象所有属性,包括静态方法和静态属性
  Object.getOwnPropertyNames(Original).forEach(key => {
    // Skip the properties that should not be overwritten
    // 跳过不覆盖的浏览器属性
    if (shouldIgnore[key]) {
      return
    }

    // Some browsers does not allow reconfigure built-in properties
    // 某些浏览器不允许重新配置属性
    const extendedDescriptor = Object.getOwnPropertyDescriptor(Extended, key)
    if (extendedDescriptor && !extendedDescriptor.configurable) {
      return
    }

    // 原对象上类型描述
    const descriptor = Object.getOwnPropertyDescriptor(Original, key)!

    // If the user agent does not support `__proto__` or its family (IE <= 10),
    // the sub class properties may be inherited properties from the super class in TypeScript.
    // We need to exclude such properties to prevent to overwrite
    // the component options object which stored on the extended constructor (See #192).
    // If the value is a referenced value (object or function),
    // we can check equality of them and exclude it if they have the same reference.
    // If it is a primitive value, it will be forwarded for safety.
    if (!hasProto) {
      // Only `cid` is explicitly exluded from property forwarding
      // because we cannot detect whether it is a inherited property or not
      // on the no `__proto__` environment even though the property is reserved.
      if (key === 'cid') {
        return
      }

      // 父类上属性描述
      const superDescriptor = Object.getOwnPropertyDescriptor(Super, key)

      //检查属性是否继承自父类,如果是引用类型的属性且值相同,就直接跳过处理
      if (
        !isPrimitive(descriptor.value) &&
        superDescriptor &&
        superDescriptor.value === descriptor.value
      ) {
        return
      }
    }

    // Warn if the users manually declare reserved properties
    // 如果用户手动声明保留属性,则发出警告
    if (
      process.env.NODE_ENV !== 'production' &&
      reservedPropertyNames.indexOf(key) >= 0
    ) {
      warn(
        `Static property name '${key}' declared on class '${Original.name}' ` +
        'conflicts with reserved property name of Vue internal. ' +
        'It may cause unexpected behavior of the component. Consider renaming the property.'
      )
    }

    //对属性的继承和扩展。这样,拓展后的组件类就包含了原始组件类的静态属性和方法,同时也可以自定义新的属性和方法
    Object.defineProperty(Extended, key, descriptor)
  })
}

mixins ​

js
export function mixins(...Ctors: VueClass<Vue>[]): VueClass<Vue> {
  return Vue.extend({ mixins: Ctors });
}

collectDataFromConstructor ​

返回收集到的类属性的普通数据对象 plainData,该对象可以在 Vue 组件中作为 data 选项使用,实现对类属性的响应式处理。

js
export function collectDataFromConstructor(
  vm: Vue,
  Component: VueClass<Vue>
) {
  // override _init to prevent to init as Vue instance
  const originalInit = Component.prototype._init;
  Component.prototype._init = function (this: Vue) {
    // proxy to actual vm
    const keys = Object.getOwnPropertyNames(vm);
    // 2.2.0 compat (props are no longer exposed as self properties)
    if (vm.$options.props) {
      for (const key in vm.$options.props) {
        if (!vm.hasOwnProperty(key)) {
          keys.push(key);
        }
      }
    }
    // 对所有属性做代理
    //在 Vue 组件中,data 选项中的属性会被 Vue 自动进行响应式处理,即当数据发生变化时,会自动更新视图。然而,在使用类语法定义组件时,类属性并不会自动成为响应式数据,因为 Vue 不会对类属性进行特殊处理。

    //为了让类属性也能实现响应式,这段代码的作用是在组件实例创建时,通过代理的方式,将类属性的读取和赋值操作映射到实际的响应式数据对象 vm 上。这样,当我们在组件中读取和修改类属性时,实际上是在读取和修改 vm 实例中的对应属性,从而触发 Vue 的响应式机制,实时更新视图
    keys.forEach((key) => {
      Object.defineProperty(this, key, {
        get: () => vm[key],
        set: (value) => {
          vm[key] = value;
        },
        configurable: true
      });
    });
  };

  // should be acquired class property values
  const data = new Component();

  // restore original _init to avoid memory leak (#209)
  Component.prototype._init = originalInit;

  // create plain data object
  const plainData = {};

  // 遍历new出来的对象,把所有属性添加到空对象里面
  Object.keys(data).forEach((key) => {
    if (data[key] !== undefined) {
      plainData[key] = data[key];
    }
  });

  if (process.env.NODE_ENV !== 'production') {
    if (
      !(Component.prototype instanceof Vue) &&
      Object.keys(plainData).length > 0
    ) {
      warn(
        'Component class must inherit Vue or its descendant class ' +
          'when class property is used.'
      );
    }
  }

  return plainData;
}