diff --git a/src/component.ts b/src/component.ts index 54a8192..cadff55 100644 --- a/src/component.ts +++ b/src/component.ts @@ -21,30 +21,54 @@ export const $internalHooks = [ 'serverPrefetch' // 2.6 ] +/** + * 组件工厂函数 + * @param Component 我们所编写的class + * @param options 组件选项 + * @returns 通过Vue.extends得到的组件 + */ export function componentFactory ( Component: VueClass, options: ComponentOptions = {} ): VueClass { options.name = options.name || (Component as any)._componentTag || (Component as any).name // prototype props. + // 获得class上所有在原形上的属性 const proto = Component.prototype + // 把这些属性给遍历一遍 Object.getOwnPropertyNames(proto).forEach(function (key) { if (key === 'constructor') { return } // hooks + // 如果属性名是生命周期钩子名称名,那么就把对应属性值赋到option里 if ($internalHooks.indexOf(key) > -1) { options[key] = proto[key] return } + // 获得原型上各个属性描述符 const descriptor = Object.getOwnPropertyDescriptor(proto, key)! + + // 如果属性值不是undefined if (descriptor.value !== void 0) { // methods + /** + * 如果属性值为函数,那么就把对应属性值赋到option.methods里 + */ if (typeof descriptor.value === 'function') { (options.methods || (options.methods = {}))[key] = descriptor.value } else { // typescript decorated data + /** + * 如果属性值是正常的值,那就通过mixin把原有属性值给混入进去 + * + * 一般来说,使用class语法,prototype上不会存在属性值 + * + * 因此准确来说,这里处理一些额外的情况而导致的、使得prototype上出现属性值的改变,例如: + * 1. 对属性使用了属性装饰器 + * 2. 在外部通过Component.prototype手动更改了原型 + */ (options.mixins || (options.mixins = [])).push({ data (this: Vue) { return { [key]: descriptor.value } @@ -52,6 +76,11 @@ export function componentFactory ( }) } } else if (descriptor.get || descriptor.set) { + /** + * 如果属性描述符中包含get、set,那么就认为它是计算属性,因此赋值到option.computed里 + * + * 值得注意的是:使用class语法,prototype上会存在get/set计算属性 + */ // computed properties (options.computed || (options.computed = {}))[key] = { get: descriptor.get, @@ -61,8 +90,34 @@ export function componentFactory ( }) // add data hook to collect class properties as Vue instance's data + // 添加data mixin,以收集类的属性,来作为Vue实例的数据 ;(options.mixins || (options.mixins = [])).push({ data (this: Vue) { + /** + * 由于使用class语法,prototype上不会存在属性,只存在方法(属性将会在constructor中赋值): + * + * class A { + * constructor () { + * console.log(this.a, this.b) + * } + * a = 1 + * b = 2 + * } + * + * 相当于 -> + * + * class A { + * constructor() { + * this.a = 1; + * this.b = 2; + * console.log(this.a, this.b); + * } + * } + * + * 因此要拿到在类中写的属性,就需要将类在下列调用中实例化一次我们通过class定义的类 + */ + // this 为vm,Vue实例(真实实例) + // Component 为我们通过class定义的类 return collectDataFromConstructor(this, Component) } }) @@ -75,18 +130,22 @@ export function componentFactory ( } // find super + // 查找父类 const superProto = Object.getPrototypeOf(Component.prototype) const Super = superProto instanceof Vue ? superProto.constructor as VueClass : Vue const Extended = Super.extend(options) + // TODO: forwardStaticMembers(Extended, Component, Super) + // 如果支持reflect-metadata,则复制metadata if (reflectionIsSupported()) { copyReflectionMetadata(Extended, Component) } + // 经过以上一堆操作,就得到了一个被extend后组件类 return Extended } @@ -116,6 +175,12 @@ const shouldIgnore = { caller: true } +/** + * 转发静态成员 + * @param Extended TODO: 扩展? + * @param Original TODO: 原始? + * @param Super TODO: 父类? + */ function forwardStaticMembers ( Extended: typeof Vue, Original: typeof Vue, diff --git a/src/data.ts b/src/data.ts index ec896ca..53ac046 100644 --- a/src/data.ts +++ b/src/data.ts @@ -2,13 +2,31 @@ import Vue from 'vue' import { VueClass } from './declarations' import { warn } from './util' +/** + * 从构造函数收集data + * @param vm Vue实例(真实实例) + * @param Component 我们通过class定义的类 + * @returns 纯数据 + */ export function collectDataFromConstructor (vm: Vue, Component: VueClass) { // override _init to prevent to init as Vue instance + /** + * 我们这里仅仅是获取data,不需要进行其他操作。因此,首先我们: + * + * 覆盖类中原有的_init方法并暂存,避免被实例化为一个Vue实例 + * + * _init中包含有真实的组件生成、挂载逻辑,我们这里只需要取到data,不需要做更多的事情 + */ const originalInit = Component.prototype._init Component.prototype._init = function (this: Vue) { + /** + * Component.prototype._init函数中的this指向我们通过class定义的类的实例 + */ // proxy to actual vm + // 获取vm上的key;这些key将会被以代理到真实的vm上 const keys = Object.getOwnPropertyNames(vm) // 2.2.0 compat (props are no longer exposed as self properties) + // TODO: ??? if (vm.$options.props) { for (const key in vm.$options.props) { if (!vm.hasOwnProperty(key)) { @@ -16,6 +34,10 @@ export function collectDataFromConstructor (vm: Vue, Component: VueClass) { } } } + // 将Vue实例(真实实例)中的key赋值给class定义的类的实例 + /** + * TODO: 看起来这是collectDataFromConstructor的副作用? + */ keys.forEach(key => { Object.defineProperty(this, key, { get: () => vm[key], @@ -26,12 +48,16 @@ export function collectDataFromConstructor (vm: Vue, Component: VueClass) { } // should be acquired class property values + // 通过实例化组件,来获得data + // new 流程中会调用上方覆写掉的 _init const data = new Component() // restore original _init to avoid memory leak (#209) + // 恢复原始 _init 来避免内存泄露 Component.prototype._init = originalInit // create plain data object + // 建立一个纯的、用于存储data的对象 const plainData = {} Object.keys(data).forEach(key => { if (data[key] !== undefined) { diff --git a/src/index.ts b/src/index.ts index d2b955b..ee6c703 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,6 +4,14 @@ import { componentFactory, $internalHooks } from './component' export { createDecorator, VueDecorator, mixins } from './util' +/** + * Component 装饰器入口 + * + * vue-class-component实质: + * 将class风格组件中的类成员转换为vue组件option中对应的内容,并最终使用Vue.extends来获得组件构造函数 + * —— 因此最终用于构造组件的是通过Vue.extends获得的组件函数,而不是我们定义的class + * @param options Vue options + */ function Component (options: ComponentOptions & ThisType): >(target: VC) => VC function Component >(target: VC): VC function Component (options: ComponentOptions | VueClass): any { diff --git a/src/reflect.ts b/src/reflect.ts index ab65dff..e3b04cc 100644 --- a/src/reflect.ts +++ b/src/reflect.ts @@ -12,12 +12,15 @@ export function copyReflectionMetadata ( to: VueConstructor, from: VueClass ) { + // 转发组件构造函数(相当于类)的metadata forwardMetadata(to, from) + // 转发原型上的metadata Object.getOwnPropertyNames(from.prototype).forEach(key => { forwardMetadata(to.prototype, from.prototype, key) }) + // TODO: 转发类静态方法的metadata? Object.getOwnPropertyNames(from).forEach(key => { forwardMetadata(to, from, key) }) diff --git a/src/util.ts b/src/util.ts index 846a836..685286b 100644 --- a/src/util.ts +++ b/src/util.ts @@ -17,7 +17,18 @@ export interface VueDecorator { (target: Vue, key: string, index: number): void } -export function createDecorator (factory: (options: ComponentOptions, key: string, index: number) => void): VueDecorator { +/** + * 创建装饰器 + * @param factory 装饰器工厂函数 + * @returns 用于在构造函数装饰器队列中插入装饰器的函数 + */ +export function createDecorator ( + factory: ( + options: ComponentOptions, // 组件选项 + key: string, // class中被装饰的key + index: number + ) => void +): VueDecorator { return (target: Vue | typeof Vue, key?: any, index?: any) => { const Ctor = typeof target === 'function' ? target as DecoratedClass @@ -28,6 +39,7 @@ export function createDecorator (factory: (options: ComponentOptions, key: if (typeof index !== 'number') { index = undefined } + // 函数装饰器队列中的函数,参数为options,即组件选项 Ctor.__decorators__.push(options => factory(options, key, index)) } } @@ -61,6 +73,8 @@ export function mixins (...Ctors: VueClass[]): VueClass { export function isPrimitive (value: any): boolean { const type = typeof value + // 判读一个值是不是原始类型: + // value为null,或type不是object/function return value == null || (type !== 'object' && type !== 'function') }