从去年年初,咱们开始在团队内部推行TS,TS的好处不言而喻,虽然当时有些同窗提出一些异议,但最终仍是利大于弊,咱们成功迈出了第一步。
咱们前期也作了相关调研,vue对TS的支持并非很好,必须使用第三方库来支持;相比较而言,react由于自己就支持函数和类,和TS搭配起来简直是绝配;而vue2说到底实际上是配置形式,只能是局部支持。最终咱们选择了vue-property-decorator(好像也只有这个库支持😁),这篇文章主要是解释下vue是如何支持TS的(虽然最终的结果……)vue
至于如何在Vue项目中接入TS,网上资料不少,在这就很少作介绍了,各位看官可自行google。这里主要介绍这个库vue-property-decorator,使用过这个库的同窗们有没有以下疑问:react
vue class-style形式的组件和正常的class类有什么不一样?好比说没有constructor函数?vue-router
为何class-style形式的组件,不须要new操做就可使用?vuex
这些问题是否是你们都没想过😁。bash
由于vue-property-decorator中的@Component装饰器是直接从vue-class-component导出,因此就直接看vue-class-component是如何实现的。app
入口文件,这里把装饰器函数和装饰器工厂函数合成一个。ide
registerHooks是为了vue的插件准备的,会注册到vue实例上,能够日后看。 咱们使用的vue-router、vue-apollo、vuex等等这些库在实例上的方法,都须要在这里注册(估计不少人在这里吃了亏)。函数
import { componentFactory, $internalHooks } from './component';
export { createDecorator, mixins } from './util';
function Component(options) {
if (typeof options === 'function') {
return componentFactory(options);
}
return function (Component) {
return componentFactory(Component, options);
};
}
Component.registerHooks = function registerHooks(keys) {
$internalHooks.push.apply($internalHooks, keys);
};
export default Component;
复制代码
而后咱们看看component.js的源码,很容易理解,你们看看就行, 没什么可讲的,重要的点我都写了注释。fetch
// vue内置钩子函数,用来注册到vue实例上
export var $internalHooks = [
'data',
'beforeCreate',
'created',
'beforeMount',
'mounted',
'beforeDestroy',
'destroyed',
'beforeUpdate',
'updated',
'activated',
'deactivated',
'render',
'errorCaptured',
'serverPrefetch' // 2.6
];
// 这就是导出的装饰器函数/装饰器工厂函数,不了解的能够看一下阮神的ES6。
// 这里的options,就是@Component传入的参数
export function componentFactory(Component, options) {
if (options === void 0) { options = {}; }
options.name = options.name || Component._componentTag || Component.name;
// prototype props.
var proto = Component.prototype;
Object.getOwnPropertyNames(proto).forEach(function (key) {
// 这就是为何写组件类时,没有构造函数的缘由
if (key === 'constructor') {
return;
}
// hooks,内置的钩子函数一一挂载到实例上
if ($internalHooks.indexOf(key) > -1) {
options[key] = proto[key];
return;
}
// Object.getOwnPropertyDescriptor() 方法返回指定对象上一个自有属性对应的属性描述符。
var descriptor = Object.getOwnPropertyDescriptor(proto, key);
if (descriptor.value !== void 0) {
// 处理methods,若是是函数都放在methods中
if (typeof descriptor.value === 'function') {
(options.methods || (options.methods = {}))[key] = descriptor.value;
}
else {
//处理 data属性
(options.mixins || (options.mixins = [])).push({
data: function () {
var _a;
return _a = {}, _a[key] = descriptor.value, _a;
}
});
}
}
else if (descriptor.get || descriptor.set) {
// 处理computed,变成get/set
(options.computed || (options.computed = {}))[key] = {
get: descriptor.get,
set: descriptor.set
};
}
});
(options.mixins || (options.mixins = [])).push({
// collectDataFromConstructor这个方法在下面有解释
data: function () {
return collectDataFromConstructor(this, Component);
}
});
// decorate options
var decorators = Component.__decorators__;
if (decorators) {
decorators.forEach(function (fn) { return fn(options); });
delete Component.__decorators__;
}
// find super
var superProto = Object.getPrototypeOf(Component.prototype);
var Super = superProto instanceof Vue
? superProto.constructor
: Vue;
var Extended = Super.extend(options);
forwardStaticMembers(Extended, Component, Super);
if (reflectionIsSupported) {
copyReflectionMetadata(Extended, Component);
}
return Extended;
}
复制代码
而后是data.js,这里是在处理props相关逻辑。ui
import Vue from 'vue';
import { warn } from './util';
export function collectDataFromConstructor(vm, Component) {
// override _init to prevent to init as Vue instance
var originalInit = Component.prototype._init;
Component.prototype._init = function () {
var _this = this;
// proxy to actual vm
var keys = Object.getOwnPropertyNames(vm);
// 2.2.0 compat (props are no longer exposed as self properties)
if (vm.$options.props) {
for (var key in vm.$options.props) {
if (!vm.hasOwnProperty(key)) {
keys.push(key);
}
}
}
keys.forEach(function (key) {
if (key.charAt(0) !== '_') {
Object.defineProperty(_this, key, {
get: function () { return vm[key]; },
set: function (value) { vm[key] = value; },
configurable: true
});
}
});
};
// should be acquired class property values
var data = new Component();
// restore original _init to avoid memory leak (#209)
Component.prototype._init = originalInit;
// create plain data object
var plainData = {};
Object.keys(data).forEach(function (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;
}
复制代码
看完是否明白为何有些属性要放到@Component,而有些须要放到class里面去写?
文章前面的两个问题,也应该明白了。
经过源码来看,这两个库主要是把class-style形式改为vue2配置模板形式。 使用class-style写法使vue支持ts,而后经过装饰器再转成vue原生写法传给loader处理。说白了其实就是一层障眼法,好让你们开开心心的使用ts。但其实根本问题并无解决,这还须要等待尤大的vue3出来解决😂