From d5da2e81be414992effe28d224a441952fd396e7 Mon Sep 17 00:00:00 2001 From: gogoend Date: Sat, 4 Jun 2022 22:26:15 +0800 Subject: [PATCH 1/5] =?UTF-8?q?docs:=20=E4=BA=86=E8=A7=A3=E7=B1=BB?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=E8=BD=AC=E6=8D=A2=E5=88=B0vue=20option?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=E7=9A=84=E4=B8=BB=E8=A6=81=E6=B5=81=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 了解从类的prototype属性类到methods、computed的对应转换 2. 了解通过覆写Vue.prototype._init,来实例化类,获取data并进行转换的过程 --- src/component.ts | 51 ++++++++++++++++++++++++++++++++++++++++++++++++ src/data.ts | 26 ++++++++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/src/component.ts b/src/component.ts index 54a8192..44d882d 100644 --- a/src/component.ts +++ b/src/component.ts @@ -27,24 +27,42 @@ export function componentFactory ( ): 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 +70,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 +84,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,6 +124,7 @@ export function componentFactory ( } // find super + // 查找父类 const superProto = Object.getPrototypeOf(Component.prototype) const Super = superProto instanceof Vue ? superProto.constructor as VueClass @@ -87,6 +137,7 @@ export function componentFactory ( copyReflectionMetadata(Extended, Component) } + // 经过以上一堆操作,就得到了一个被extend后组件类 return Extended } 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) { From d0d878ffa5207237a2fc519b1ed518c88338d1b3 Mon Sep 17 00:00:00 2001 From: gogoend Date: Sat, 4 Jun 2022 22:35:42 +0800 Subject: [PATCH 2/5] =?UTF-8?q?docs:=20=E4=BA=86=E8=A7=A3isPrimitive?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/util.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/util.ts b/src/util.ts index 846a836..2e8ebae 100644 --- a/src/util.ts +++ b/src/util.ts @@ -61,6 +61,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') } From 2d82dda29750ee6ca08c22acdda78e1d77ed1c63 Mon Sep 17 00:00:00 2001 From: gogoend Date: Sun, 5 Jun 2022 09:56:34 +0800 Subject: [PATCH 3/5] =?UTF-8?q?docs:=20=E4=BA=86=E8=A7=A3Component?= =?UTF-8?q?=E8=A3=85=E9=A5=B0=E5=99=A8=E5=AE=9E=E8=B4=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/component.ts | 6 ++++++ src/index.ts | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/src/component.ts b/src/component.ts index 44d882d..bd90da1 100644 --- a/src/component.ts +++ b/src/component.ts @@ -21,6 +21,12 @@ export const $internalHooks = [ 'serverPrefetch' // 2.6 ] +/** + * 组件工厂函数 + * @param Component 我们所编写的class + * @param options 组件选项 + * @returns 通过Vue.extends得到的组件 + */ export function componentFactory ( Component: VueClass, options: ComponentOptions = {} 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 { From 9671e02d5850ddfe1067c2b46ba50bd3d8ecdc63 Mon Sep 17 00:00:00 2001 From: gogoend Date: Wed, 8 Jun 2022 01:32:15 +0800 Subject: [PATCH 4/5] =?UTF-8?q?feat(createDecorator):=20=E4=BA=86=E8=A7=A3?= =?UTF-8?q?createDecorator=E7=9A=84=E5=8E=9F=E7=90=86=E5=8F=8A=E5=85=B6?= =?UTF-8?q?=E4=B8=AD=E5=9B=9E=E8=B0=83=E7=9A=84=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/util.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/util.ts b/src/util.ts index 2e8ebae..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)) } } From af6aea0b8e2c67d26e969c8c8c27d41e7770507a Mon Sep 17 00:00:00 2001 From: gogoend Date: Sat, 17 Sep 2022 20:26:27 +0800 Subject: [PATCH 5/5] =?UTF-8?q?docs:=20=E4=B8=BAcopyReflectionMetadata?= =?UTF-8?q?=E5=87=BD=E6=95=B0=E5=8A=A0=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/component.ts | 8 ++++++++ src/reflect.ts | 3 +++ 2 files changed, 11 insertions(+) diff --git a/src/component.ts b/src/component.ts index bd90da1..cadff55 100644 --- a/src/component.ts +++ b/src/component.ts @@ -137,8 +137,10 @@ export function componentFactory ( : Vue const Extended = Super.extend(options) + // TODO: forwardStaticMembers(Extended, Component, Super) + // 如果支持reflect-metadata,则复制metadata if (reflectionIsSupported()) { copyReflectionMetadata(Extended, Component) } @@ -173,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/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) })