你不知道之vue如何支持TS

背景

从去年年初,我们开始在团队内部推行TS,TS的好处不言而喻,虽然当时有些同学提出一些异议,但最终还是利大于弊,我们成功迈出了第一步。
我们前期也做了相关调研,vue对TS的支持并不是很好,必须使用第三方库来支持;相比较而言,react因为本身就支持函数和类,和TS搭配起来简直是绝配;而vue2说到底其实是配置形式,只能是局部支持。最终我们选择了vue-property-decorator(好像也只有这个库支持😁),这篇文章主要是解释下vue是如何支持TS的(虽然最终的结果……)

问题

至于如何在Vue项目中接入TS,网上资料很多,在这就不多做介绍了,各位看官可自行google。这里主要介绍这个库vue-property-decorator,使用过这个库的同学们有没有如下疑问:

  • vue class-style形式的组件和正常的class类有什么不同?比如说没有constructor函数?

  • 为什么class-style形式的组件,不需要new操作就可以使用?

这些问题是不是大家都没想过😁。

源码解析

vue-class-component源码

因为vue-property-decorator中的@Component装饰器是直接从vue-class-component导出,所以就直接看vue-class-component是如何实现的。

入口文件,这里把装饰器函数和装饰器工厂函数合成一个。

registerHooks是为了vue的插件准备的,会注册到vue实例上,可以往后看。 我们使用的vue-router、vue-apollo、vuex等等这些库在实例上的方法,都需要在这里注册(估计很多人在这里吃了亏)。

import { componentFactory, $internalHooks } from './component';
export { createDecorator, mixins } from './util';
function Component(options) {
    if (typ 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的源码,很容易理解,大家看看就行, 没什么可讲的,重要的点我都写了注释。

// 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 (typ 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.getPrototyp(Component.prototype);
    var Super = superProto instanc Vue
        ? superProto.constructor
        : Vue;
    var Extended = Super.extend(options);
    forwardStaticMembers(Extended, Component, Super);
    if (reflectionIsSupported) {
        copyReflectionMetadata(Extended, Component);
    }
    return Extended;
}

然后是data.js,这里是在处理props相关逻辑。

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 instanc 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出来解决😂

原文链接:juejin.im

上一篇:记一次大文件分片上传
下一篇:Node.js + TypeScript 开发服务端是什么样的体验

相关推荐

官方社区

扫码加入 JavaScript 社区