在使用vue-cli创建项目时,选择vue3.X和Typescript后会发现,项目搭配了vue-class-component使用,且版本为^8.0.0-0。然而官方v8文档没有出,接下来对这个库做个入门分析,看看它是如何处理类组件的。
为什么用vue-class-component?
vue2 对 TS 的支持并不友好,原因在于vue2的Option API风格。options是个简单对象,而ts是一种类型系统、面向对象的语法。两者有点不匹配。所以 vue3 要跟 TS 的整合,通常使用 vue-class-component , 用基于 class(类) 的组件书写方式。
当然不使用vue-class-component也是可以的,只是当项目开发到一定复杂度之后,太多的文件和逻辑不容易维护和阅读,不如类面向对象的特点,逻辑清晰、模块化,且有继承等高级特性。
vue-class-component是什么?
vue-class-component 是 vue 的官方库,作用是用类的方式编写组件。通过以类样式定义组件,不仅可以更改语法,而且还可以利用一些 ECMAScript 语言特性,如类继承和装饰器。
装饰器是什么?
装饰器(Decorator)用来增强 JavaScript 类(class)的功能,许多面向对象的语言都有这种语法,目前有一个提案将其引入了 ECMAScript。
装饰器是一种函数,写成@ + 函数名。它可以放在类和类方法的定义前面。
这里仅介绍下类装饰器
//装饰器是一个函数,API 的类型描述如下。
//装饰器函数调用时,会接收到两个参数。
- `value`:被装饰的值,某些情况下可能是`undefined`(装饰属性时)。
- `context`:一个对象,包含了被装饰的值的上下文信息。
@decorator
class A {}
// 等同于
class A {}
A = decorator(A) || A;
//不含参数
@testable
class MyTestableClass {
// ...
}
function testable(target) {
target.isTestable = true;
}
MyTestableClass.isTestable // true
//含参数
function testable(isTestable) {
return function(target) {
target.isTestable = isTestable;
}
}
@testable(true)
class MyTestableClass {}
MyTestableClass.isTestable // true
@testable(false)
class MyClass {}
MyClass.isTestable // false
注意,装饰器对类的行为的改变,是代码编译时发生的,而不是在运行时。这意味着,装饰器能在编译阶段运行代码。也就是说,装饰器本质就是编译时执行的函数。
vue-class-component的处理逻辑?
我们看到,VueImpl立即执行函数目的是在其构造函数中注入registerHooks with __vccOpts三个属性,__vccOpts属性在Vue中用来判断是不是类组件。
再看看Options装饰器
接着执行到export default XXX处,会在Vue-loader中获取__vccOpts属性,从而触发__vccOpts的get方法,生成options对象,这个对象就等价于vue2中的配置项了。
get: function get() {
if (this === Vue) {
return {};
}
//当前类组件
var Ctor = this;
//查找有没有__c属性
var cache = getOwn(Ctor, '__c');
if (cache) {
return cache;
} // If the options are provided via decorator use it as a base
//可以简单理解为对象复制
var options = _objectSpread2({}, getOwn(Ctor, '__o'));
Ctor.__c = options; // 增加options属性
//查找原型链继承的 VueImpl
var Super = getSuper(Ctor);
if (Super) {
options["extends"] = Super.__vccOpts;
} // Inject base options as a mixin
//查找__b属性
var base = getOwn(Ctor, '__b');
if (base) {
options.mixins = options.mixins || [];
options.mixins.unshift(base);
}
options.methods = _objectSpread2({}, options.methods);
options.computed = _objectSpread2({}, options.computed);
var proto = Ctor.prototype;
//遍历原型上的属性
Object.getOwnPropertyNames(proto).forEach(function (key) {
if (key === 'constructor') {
return;
} // hooks
//查找原型上的属性有没有在__h内,如果有赋值给options对应属性上
if (Ctor.__h.indexOf(key) > -1) {
options[key] = proto[key];
return;
}
//获取属性的属性描述符
var descriptor = Object.getOwnPropertyDescriptor(proto, key); // methods
if (typeof descriptor.value === 'function') {
options.methods[key] = descriptor.value;
return;
} // computed properties
if (descriptor.get || descriptor.set) {
options.computed[key] = {
get: descriptor.get,
set: descriptor.set
};
return;
}
});
options.setup = function (props, ctx) {
/**省略**/
};
var decorators = getOwn(Ctor, '__d');
if (decorators) {
decorators.forEach(function (fn) {
return fn(options);
});
} // from Vue Loader
var injections = ['render', 'ssrRender', '__file', '__cssModules', '__scopeId', '__hmrId'];
injections.forEach(function (key) {
if (Ctor[key]) {
options[key] = Ctor[key];
}
});
return options;
}
后话:接着会执行createApp(App), app.mount('#app'),在mount方法中会调用setup方法,接着执行组件更新、组件渲染,最终将真实dom挂载到页面上。