從去年年初,咱們開始在團隊內部推行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出來解決😂