你不知道之vue如何支持TS

背景

從去年年初,咱們開始在團隊內部推行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-class-component源碼

由於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出來解決😂

相關文章
相關標籤/搜索