vue 并不是类的写法,要想实现需借助 vue-class-component
或者 vue-property-decorator
,例如:
import { Vue, Component, Prop } from 'vue-property-decorator'
@Component
export default class YourComponent extends Vue {
@Prop(Number) readonly propA!: number
@Prop({ default: 'default value' }) readonly propB!: string
@Prop([String, Boolean]) readonly propC!: string | boolean
}
可以看到装饰器的场景就很广泛了,包括现在使用很广泛的库 core-decorators
。另外类的写法对支持 ts 更友好。
从 tc39 上看到目前该提案还处于 stage 2
,最后一次提交提案是在 January 2019
,看来到真正定稿还需要一段时间。
插件 @babel/plugin-proposal-decorators
支持 2 种模式:
legacy: true
在 es7 提出 Decorator 时,babel 就做过支持,有过一个转化的版本decoratorsBeforeExport: true
如今处于 stage 2 后 babel 又做了一个与之前完全不同的转化版本
{
"plugins": [
["@babel/plugin-proposal-decorators", {
"legacy": false,
"decoratorsBeforeExport": true
}]
]
}
另外需要注意的是,如果使用老版本的 Decorator ,需要为 @babel/plugin-proposal-class-properties
添加 option { "loose" : true }
,才能兼容 class 写法。
{
"plugins": [
["@babel/plugin-proposal-decorators", { "legacy": true }],
["@babel/plugin-proposal-class-properties", { "loose" : true }]
]
}
源码为
class C {
@unenumerable
@readonly
method () {}
}
转化后的源码
var _class;
/**
* 赋值调用Decorator,从函数语义上也可以看出来
* @param target {Object} 类的原型 class.prototype
* @param property {String} 方法或属性的名称
* @param decorators {Array} 装饰器数组
* @param descriptor {Object} 对象的属性描述
* @param context {Object} 类属性的初始值方法赋值调用需要的上下文
* @returns descriptor {Object} 对象的属性描述,用于初始化属性值
*/
function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) {
// 第二部分
// 属性描述的拷贝
// 属性分为 数据属性 和 访问属性,二者公共的部分是 configurable 和 enumerable
var desc = {};
Object.keys(descriptor)
.forEach(function (key) {
desc[key] = descriptor[key];
});
desc.enumerable = !!desc.enumerable;
desc.configurable = !!desc.configurable;
// 如果该属性为类的属性值,也就是数据属性
// 或者该值有被初始化
if ('value' in desc || desc.initializer) {
desc.writable = true;
}
// 第三部分
// 可以看到装饰器是写法上从下至上调用的
// 然后从左至右调用了装饰器,并得到最终的属性描述对象
desc = decorators.slice()
.reverse()
.reduce(function (desc, decorator) {
return decorator(target, property, desc) || desc;
}, desc);
// 初始化类的属性值
if (context && desc.initializer !== void 0) {
desc.value = desc.initializer ? desc.initializer.call(context) : void 0;
desc.initializer = undefined;
}
// 将最终的属性描述对象挂载到该属性上
if (desc.initializer === void 0) {
Object.defineProperty(target, property, desc);
desc = null;
}
return desc;
}
// 第一部分,赋值调用,等同于
// _class = class C { method() {} }
// 如果有多个属性和方法,则调用下面这个方法多次
// _applyDecoratedDescriptor(_class.prototype, ...)
// let C = _class
let C = (_class = class C { method() {} },
(_applyDecoratedDescriptor(
_class.prototype,
"method",
[unenumerable, readonly],
Object.getOwnPropertyDescriptor(_class.prototype, "method"),
_class.prototype
)),
_class
);
function readonly(target, name, descriptor) {
descriptor.writable = false;
return descriptor;
}
function unenumerable(target, name, descriptor) {
descriptor.enumerable = false;
return descriptor;
}
当源码新增静态属性赋值时
class C {
@readonly
name = 'ym'
@unenumerable
@readonly
method () {}
}
装饰器的方法调用变为
_descriptor = _applyDecoratedDescriptor(_class.prototype, "name", [readonly], {
configurable: true,
enumerable: true,
writable: true,
// 初始化值的方法
initializer: function () {
return 'ym';
}
// 没有初始化上下文了
})
源码为
class C {
@unenumerable
@readonly
method () {}
@unenumerable
getData () {}
}
转化后,部分代码
function _decorate(decorators, factory, superClass, mixins) {
// 1.根据decorators,转化成标准的 element
// 2.去重聚合 element,得到新的 newElements
// {
// decorators: [ƒ],
// descriptor: {value: ƒ, writable: true, configurable: true, enumerable: false},
// key: "getData",
// kind: "method",
// placement: "prototype",
// }
// 3.然后通过 decorateElement 反向调用element.decorators中的 decorator,
// 与此同时,产生finishers 和 extras { element: element, finishers: finishers, extras: extras }
// 最终导出成最终的格式 newElements
// { element: element, finishers: finishers }
// 4.initializeClassElements,将被装饰器调用过的属性和方法重新挂载到类上
var decorated = api.decorateClass(_coalesceClassElements(r.d.map(_createElementDescriptor)), decorators);
api.initializeClassElements(r.F, decorated.elements);
return api.runClassFinishers(r.F, decorated.finishers);
}
let C = _decorate(null, function (_initialize) {
class C {
constructor() {
_initialize(this);
}
}
return {
F: C,
d: [{
kind: "method",
decorators: [unenumerable, readonly],
key: "method",
value: function method() {}
}, {
kind: "method",
decorators: [unenumerable],
key: "getData",
value: function getData() {}
}]
};
});
还未弄清楚的是 finisher 和 extra 的作用。另外需要注意的是,如果给类添加静态属性,是需要使用@babel/plugin-proposal-class-properties
进行转化的。
新的装饰器转换支持了 get
和 set
,例如
class A {
get name() {}
}
- log 函数
- autobind 上下文
- debounce 防抖
- mixin 混入新的类方法
甚至是 异步请求的 loading 或 结果的 toast、message 都可以用上 Decorator。
Decorator 提供了一种抽象复用代码的更优雅的方式,但是应用也需要考虑场景,不能随意使用,就像async/await
函数一样,滥用会得不偿失。目前该提案处于 stage 2阶段,还不够稳定,学习的目的是为了拥抱未来的趋势,使用 Vue 中 class-component
的写法和 ts。