变成我们需要使用装饰器将 TypeScript class 转换为有效的 Vue 组件?
Into what we need to convert the TypeScript class using decorators to get the valid Vue component?
如果我问“vue-class-component 是如何工作的”,很可能这个问题被标记为过于宽泛。顺便说一句,我查看了它的源代码,但不明白它是如何工作的,所以我需要从简单的东西开始。
这是来自 Vue documentation 的简单示例:
export default {
props: ['foo'],
created() {
console.log(this.foo)
}
}
从 ECMAScript(甚至 OOP)的角度来看,下面的 class 不等同于上面的对象。
export default class Component {
private foo!: string;
protected created(): void {
console.log(this.foo)
}
}
所以,我想问题陈述是使用如下装饰器
@MagicDecorator
class Component {
@VueProperty({ type: String })
protected foo!: string;
@VueLifecycleHook
protected created(): void {
console.log(this.foo)
}
}
将其转换为第一个列表。这个问题陈述对吗?
请注意,我没有完全照原样做的目标 vue-class-component - 欢迎改进。例如,我将向生命周期挂钩、数据和计算属性添加装饰器,这与 vue-class-component.
不同
是的,你是对的。装饰师使出了所有的魔法。它与 TypeScript 没有直接关系。仅使用 JavaScript 和 babel 的装饰器插件即可完成。我觉得vue-class-components的源码已经说明了一切,还是自己搭建一个最小版本吧。为了简单起见,我只使用 JavaScript。
我们的目标是创建一个可以将 class 转换为 vue 组件对象的装饰器,例如:
class MyComponent {
data() {
return {
count: 0,
};
}
plus() {
this.count++;
}
}
// will be converted to something like
const MyComponent = {
data() {
return {
count: 0,
};
},
methods: {
plus() {
this.count++;
}
}
}
其实很简单。我们创建一个新对象,并将class的所有方法复制到该对象中。让我们先创建装饰器函数:
function MagicDecorator(ComponentClass) {
const options = {};
return options;
}
options
就是我们转换后的结果。现在,我们要遍历 class,找出它有什么属性和方法。
function MagicDecorator(ComponentClass) {
const options = {};
Object.getOwnPropertyNames(ComponentClass.prototype).forEach((key) => {
console.log(key); // data, plus
});
return options;
}
请注意,Object.keys(ComponentClass.prototype)
将不起作用。因为这些是 non-enumerable 属性(由 Object.defineProperty()
定义)
现在,对于mounted
、created
、data
等built-in钩子方法,我们直接复制即可。您可以在 Vue 源代码中找到 hook 方法列表。
const hooks = [
'data',
'beforeCreate',
'created',
'beforeMount',
'mounted',
'beforeDestroy',
'destroyed',
'beforeUpdate',
'updated',
'activated',
'deactivated',
'render'
];
function MagicDecorator(ComponentClass) {
const options = {};
Object.getOwnPropertyNames(ComponentClass.prototype).forEach((key) => {
if (hooks.includes(key)) {
options[key] = ComponentClass.prototype[key];
}
});
return options;
}
是的,就是这么简单,只需将其复制到我们的options
。
现在,对于自定义方法,我们必须将其放在 methods
对象中。
function MagicDecorator(ComponentClass) {
const options = {
methods: {},
};
Object.getOwnPropertyNames(ComponentClass.prototype).forEach((key) => {
if (hooks.includes(key)) {
options[key] = ComponentClass.prototype[key];
return
}
if (typeof ComponentClass.prototype[key] === 'function') {
options.methods[key] = ComponentClass.prototype[key];
}
});
return options;
}
实际上现在它已经可以工作了,可以处理许多简单的组件!和上面的计数器组件一样,我们的装饰器现在完全支持它。
但是我们知道 Vue 有计算属性。让我们也支持这个功能吧。
通过 getters 和 setters 支持计算属性。现在有点棘手,因为你会注意到我们可以通过
直接访问它们
ComponentClass.prototype[key]; // This will trigger the getter
因为当您以这种方式访问它时,您实际上是在调用 getter。幸运的是,我们可以使用 Object.getOwnPropertyDescriptor()
来获得实际的 getter 和 setter 函数。然后我们需要做的就是将其放入 computed
字段。
const options = {
methods: {},
computed: {},
};
// omit...
const descriptor = Object.getOwnPropertyDescriptor(
ComponentClass.prototype,
key
);
if (descriptor.get || descriptor.set) {
options.computed[key] = {
get: descriptor.get,
set: descriptor.set
};
}
在 vue-class-components 的源代码中,它们也通过使用描述符来处理 methods
:
if (typeof descriptor.value === 'function') {
options.methods[key] = descriptor.value;
return;
}
最后,我们将不支持构造函数。我们只是将它添加到循环的第一个并忽略它:
if (key === 'constructor') {
return;
}
现在,我们有了一个完整的工作示例。在此处查看实际效果:https://codesandbox.io/s/Whosebug-vue-class-component-uhh2jg?file=/src/MagicDecorator.js
注意 1:我们的最小示例不支持使用简单 class 属性:
的数据
class MyComponent {
count = 0 // We don't support this
// Only this is supported
data() {
return { count: 0 }
}
}
如果我们想支持 class 属性,我们必须自己将其转换为响应式 属性。
注2:Babel支持两种版本的装饰器。为了与 vue-class-component 的源代码保持一致,我使用的是遗留代码。所以你必须在 @babel/plugin-proposal-decorators
插件中指定 {legacy: true}
选项。
如果我问“vue-class-component 是如何工作的”,很可能这个问题被标记为过于宽泛。顺便说一句,我查看了它的源代码,但不明白它是如何工作的,所以我需要从简单的东西开始。
这是来自 Vue documentation 的简单示例:
export default {
props: ['foo'],
created() {
console.log(this.foo)
}
}
从 ECMAScript(甚至 OOP)的角度来看,下面的 class 不等同于上面的对象。
export default class Component {
private foo!: string;
protected created(): void {
console.log(this.foo)
}
}
所以,我想问题陈述是使用如下装饰器
@MagicDecorator
class Component {
@VueProperty({ type: String })
protected foo!: string;
@VueLifecycleHook
protected created(): void {
console.log(this.foo)
}
}
将其转换为第一个列表。这个问题陈述对吗?
请注意,我没有完全照原样做的目标 vue-class-component - 欢迎改进。例如,我将向生命周期挂钩、数据和计算属性添加装饰器,这与 vue-class-component.
不同是的,你是对的。装饰师使出了所有的魔法。它与 TypeScript 没有直接关系。仅使用 JavaScript 和 babel 的装饰器插件即可完成。我觉得vue-class-components的源码已经说明了一切,还是自己搭建一个最小版本吧。为了简单起见,我只使用 JavaScript。
我们的目标是创建一个可以将 class 转换为 vue 组件对象的装饰器,例如:
class MyComponent {
data() {
return {
count: 0,
};
}
plus() {
this.count++;
}
}
// will be converted to something like
const MyComponent = {
data() {
return {
count: 0,
};
},
methods: {
plus() {
this.count++;
}
}
}
其实很简单。我们创建一个新对象,并将class的所有方法复制到该对象中。让我们先创建装饰器函数:
function MagicDecorator(ComponentClass) {
const options = {};
return options;
}
options
就是我们转换后的结果。现在,我们要遍历 class,找出它有什么属性和方法。
function MagicDecorator(ComponentClass) {
const options = {};
Object.getOwnPropertyNames(ComponentClass.prototype).forEach((key) => {
console.log(key); // data, plus
});
return options;
}
请注意,Object.keys(ComponentClass.prototype)
将不起作用。因为这些是 non-enumerable 属性(由 Object.defineProperty()
定义)
现在,对于mounted
、created
、data
等built-in钩子方法,我们直接复制即可。您可以在 Vue 源代码中找到 hook 方法列表。
const hooks = [
'data',
'beforeCreate',
'created',
'beforeMount',
'mounted',
'beforeDestroy',
'destroyed',
'beforeUpdate',
'updated',
'activated',
'deactivated',
'render'
];
function MagicDecorator(ComponentClass) {
const options = {};
Object.getOwnPropertyNames(ComponentClass.prototype).forEach((key) => {
if (hooks.includes(key)) {
options[key] = ComponentClass.prototype[key];
}
});
return options;
}
是的,就是这么简单,只需将其复制到我们的options
。
现在,对于自定义方法,我们必须将其放在 methods
对象中。
function MagicDecorator(ComponentClass) {
const options = {
methods: {},
};
Object.getOwnPropertyNames(ComponentClass.prototype).forEach((key) => {
if (hooks.includes(key)) {
options[key] = ComponentClass.prototype[key];
return
}
if (typeof ComponentClass.prototype[key] === 'function') {
options.methods[key] = ComponentClass.prototype[key];
}
});
return options;
}
实际上现在它已经可以工作了,可以处理许多简单的组件!和上面的计数器组件一样,我们的装饰器现在完全支持它。
但是我们知道 Vue 有计算属性。让我们也支持这个功能吧。
通过 getters 和 setters 支持计算属性。现在有点棘手,因为你会注意到我们可以通过
直接访问它们ComponentClass.prototype[key]; // This will trigger the getter
因为当您以这种方式访问它时,您实际上是在调用 getter。幸运的是,我们可以使用 Object.getOwnPropertyDescriptor()
来获得实际的 getter 和 setter 函数。然后我们需要做的就是将其放入 computed
字段。
const options = {
methods: {},
computed: {},
};
// omit...
const descriptor = Object.getOwnPropertyDescriptor(
ComponentClass.prototype,
key
);
if (descriptor.get || descriptor.set) {
options.computed[key] = {
get: descriptor.get,
set: descriptor.set
};
}
在 vue-class-components 的源代码中,它们也通过使用描述符来处理 methods
:
if (typeof descriptor.value === 'function') {
options.methods[key] = descriptor.value;
return;
}
最后,我们将不支持构造函数。我们只是将它添加到循环的第一个并忽略它:
if (key === 'constructor') {
return;
}
现在,我们有了一个完整的工作示例。在此处查看实际效果:https://codesandbox.io/s/Whosebug-vue-class-component-uhh2jg?file=/src/MagicDecorator.js
注意 1:我们的最小示例不支持使用简单 class 属性:
的数据class MyComponent {
count = 0 // We don't support this
// Only this is supported
data() {
return { count: 0 }
}
}
如果我们想支持 class 属性,我们必须自己将其转换为响应式 属性。
注2:Babel支持两种版本的装饰器。为了与 vue-class-component 的源代码保持一致,我使用的是遗留代码。所以你必须在 @babel/plugin-proposal-decorators
插件中指定 {legacy: true}
选项。