vue-property-decorator 源码阅读
在 vue + ts 项目中,我们一定会用到 vue-property-decorator
这个库,script
中的代码会变成下面这样:
<template>
<div id="app">
<HelloWorld msg="this is a msg from app.vue" />
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
import HelloWorld from './components/HelloWorld.vue'
@Component({
components: {
HelloWorld
}
})
export default class App extends Vue {}
</script>
通过代码的引用关系,可以发现 vue-property-decorator
的实现依赖于 vue-class-component
,它具备以下几个属性:
-
@Component
-
@Emit
-
@Inject
-
@Provice
-
@Prop
-
@Watch
-
@Model
-
Mixins
下面我们通过源码来看看,上面这些装饰器都是如何实现的。
@Component
Component
是在vue-class-component
中实现的。
import Component, { mixins } from 'vue-class-component';
下面我们来看下vue-class-component
node_modules/vue-class-component/lib/index.js
这里定义了Component
方法,判断了参数options
类型,如果不是函数类型,需要包一层函数返回componentFactory
的调用结果。
import { componentFactory, $internalHooks } from './component';
export { createDecorator, mixins } from './util';
function Component(options) {
// 这里如果是函数类型,说明 options 就是 继承于 Vue 的 class 函数
if (typeof options === 'function') {
return componentFactory(options);
}
return function (Component) {
return componentFactory(Component, options);
};
}
Component.registerHooks = function registerHooks(keys) {
$internalHooks.push(...keys);
};
export default Component;
下面是我们通常使用@Component
的方法,这里如果 options 是函数类型,说明options是App
这个 class 函数,如果不是函数类型,说明options是传入的{}
配置项。
@Component({
components: {
HelloWorld
}
})
export default class App extends Vue {}
再看下componentFactory
方法做了什么。
node_modules/vue-class-component/lib/component.js
export function componentFactory(Component, options = {}) {
options.name = options.name || Component._componentTag || Component.name;
// prototype props.
const proto = Component.prototype;
// 遍历组件原型对象上的每一个属性,根据属性值的类型,处理 hooks,methods,mixins和computed
Object.getOwnPropertyNames(proto).forEach(function (key) {
if (key === 'constructor') {
return;
}
// hooks
if ($internalHooks.indexOf(key) > -1) {
options[key] = proto[key];
return;
}
// Object.getOwnPropertyDescriptor 返回组件原型对象上自有属性对应的属性描述符
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
(options.mixins || (options.mixins = [])).push({
data() {
return { [key]: descriptor.value };
}
});
}
}
else if (descriptor.get || descriptor.set) {
// computed properties
(options.computed || (options.computed = {}))[key] = {
get: descriptor.get,
set: descriptor.set
};
}
});
(options.mixins || (options.mixins = [])).push({
data() {
return collectDataFromConstructor(this, Component);
}
});
// decorate options
const decorators = Component.__decorators__;
if (decorators) {
decorators.forEach(fn => fn(options));
delete Component.__decorators__;
}
// find super
const superProto = Object.getPrototypeOf(Component.prototype);
const Super = superProto instanceof Vue
? superProto.constructor
: Vue;
const Extended = Super.extend(options);
forwardStaticMembers(Extended, Component, Super);
if (reflectionIsSupported()) {
copyReflectionMetadata(Extended, Component);
}
return Extended;
}
node_modules/vue-class-component/lib/data.js
export function collectDataFromConstructor(vm, Component) {
// override _init to prevent to init as Vue instance
const originalInit = Component.prototype._init;
Component.prototype._init = function () {
// 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);
}
}
}
keys.forEach(key => {
if (key.charAt(0) !== '_') {
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 = {};
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;
}
@Prop
node_modules/vue-property-decorator/lib/vue-property-decorator.js
这里最核心的就是返回一个函数,即属性装饰器,在函数中对createDecorator
方法做了调用,传入当前类(vue组件)和被装饰的属性作为参数。
/**
* decorator of a prop
* @param options the options for the prop
* @return PropertyDecorator | void
*/
export function Prop(options) {
if (options === void 0) { options = {}; }
return function (target, key) {
applyMetadata(options, target, key);
createDecorator(function (componentOptions, k) {
;
(componentOptions.props || (componentOptions.props = {}))[k] = options;
})(target, key);
};
}
下面我们看看createDecorator
方法的实现。
node_modules/vue-class-component/lib/util.js
首先通过target
获取当前组件的构造函数Ctor,判断如果参数target
是一个函数,则取target
,如果target
是组件的实例对象,则取target.constructor
;
然后往构造函数的__decorators__
属性中放进传入的函数factory
。factory
的实现上面可以看到,就是将装饰器传入的参数options
挂载在组件配置选项的props
上。
export function createDecorator(factory) {
return (target, key, index) => {
const Ctor = typeof target === 'function'
? target
: target.constructor;
if (!Ctor.__decorators__) {
Ctor.__decorators__ = [];
}
if (typeof index !== 'number') {
index = undefined;
}
Ctor.__decorators__.push(options => factory(options, key, index));
};
}