vue-property-decorator 源码阅读

在 vue + ts 项目中,我们一定会用到 vue-property-decorator 这个库,script中的代码会变成下面这样:

<template>
  <div id="app">
    <HelloWorld msg="this is a msg from app.vue" />
  </div>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
import HelloWorld from './components/HelloWorld.vue'

@Component({
  components: {
    HelloWorld
  }
})
export default class App extends Vue {}
</script>

通过代码的引用关系,可以发现 vue-property-decorator 的实现依赖于 vue-class-component,它具备以下几个属性:

  • @Component

  • @Emit

  • @Inject

  • @Provice

  • @Prop

  • @Watch

  • @Model

  • Mixins

下面我们通过源码来看看,上面这些装饰器都是如何实现的。

@Component

Component是在vue-class-component中实现的。

import Component, { mixins } from 'vue-class-component';

下面我们来看下vue-class-component

node_modules/vue-class-component/lib/index.js

这里定义了Component方法,判断了参数options类型,如果不是函数类型,需要包一层函数返回componentFactory的调用结果。

import { componentFactory, $internalHooks } from './component';
export { createDecorator, mixins } from './util';
function Component(options) {
  	// 这里如果是函数类型,说明 options 就是 继承于 Vue 的 class 函数
    if (typeof options === 'function') {
        return componentFactory(options);
    }
    return function (Component) {
        return componentFactory(Component, options);
    };
}
Component.registerHooks = function registerHooks(keys) {
    $internalHooks.push(...keys);
};
export default Component;

下面是我们通常使用@Component的方法,这里如果 options 是函数类型,说明options是App这个 class 函数,如果不是函数类型,说明options是传入的{}配置项。

@Component({
  components: {
    HelloWorld
  }
})
export default class App extends Vue {}

再看下componentFactory方法做了什么。

node_modules/vue-class-component/lib/component.js

export function componentFactory(Component, options = {}) {
    options.name = options.name || Component._componentTag || Component.name;
    // prototype props.
    const proto = Component.prototype;
  	// 遍历组件原型对象上的每一个属性,根据属性值的类型,处理 hooks,methods,mixins和computed
    Object.getOwnPropertyNames(proto).forEach(function (key) {
        if (key === 'constructor') {
            return;
        }
        // hooks
        if ($internalHooks.indexOf(key) > -1) {
            options[key] = proto[key];
            return;
        }
      	// Object.getOwnPropertyDescriptor 返回组件原型对象上自有属性对应的属性描述符
        const descriptor = Object.getOwnPropertyDescriptor(proto, key);
        if (descriptor.value !== void 0) {
            // methods
            if (typeof descriptor.value === 'function') {
                (options.methods || (options.methods = {}))[key] = descriptor.value;
            }
            else {
                // typescript decorated data
                (options.mixins || (options.mixins = [])).push({
                    data() {
                        return { [key]: descriptor.value };
                    }
                });
            }
        }
        else if (descriptor.get || descriptor.set) {
            // computed properties
            (options.computed || (options.computed = {}))[key] = {
                get: descriptor.get,
                set: descriptor.set
            };
        }
    });
    (options.mixins || (options.mixins = [])).push({
        data() {
            return collectDataFromConstructor(this, Component);
        }
    });
    // decorate options
    const decorators = Component.__decorators__;
    if (decorators) {
        decorators.forEach(fn => fn(options));
        delete Component.__decorators__;
    }
    // find super
    const superProto = Object.getPrototypeOf(Component.prototype);
    const Super = superProto instanceof Vue
        ? superProto.constructor
        : Vue;
    const Extended = Super.extend(options);
    forwardStaticMembers(Extended, Component, Super);
    if (reflectionIsSupported()) {
        copyReflectionMetadata(Extended, Component);
    }
    return Extended;
}

node_modules/vue-class-component/lib/data.js

export function collectDataFromConstructor(vm, Component) {
    // override _init to prevent to init as Vue instance
    const originalInit = Component.prototype._init;
    Component.prototype._init = function () {
        // proxy to actual vm
        const keys = Object.getOwnPropertyNames(vm);
        // 2.2.0 compat (props are no longer exposed as self properties)
        if (vm.$options.props) {
            for (const key in vm.$options.props) {
                if (!vm.hasOwnProperty(key)) {
                    keys.push(key);
                }
            }
        }
        keys.forEach(key => {
            if (key.charAt(0) !== '_') {
                Object.defineProperty(this, key, {
                    get: () => vm[key],
                    set: value => { vm[key] = value; },
                    configurable: true
                });
            }
        });
    };
    // should be acquired class property values
    const data = new Component();
    // restore original _init to avoid memory leak (#209)
    Component.prototype._init = originalInit;
    // create plain data object
    const plainData = {};
    Object.keys(data).forEach(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;
}

@Prop

node_modules/vue-property-decorator/lib/vue-property-decorator.js

这里最核心的就是返回一个函数,即属性装饰器,在函数中对createDecorator方法做了调用,传入当前类(vue组件)和被装饰的属性作为参数。

/**
 * decorator of a prop
 * @param  options the options for the prop
 * @return PropertyDecorator | void
 */
export function Prop(options) {
    if (options === void 0) { options = {}; }
    return function (target, key) {
        applyMetadata(options, target, key);
        createDecorator(function (componentOptions, k) {
            ;
            (componentOptions.props || (componentOptions.props = {}))[k] = options;
        })(target, key);
    };
}

下面我们看看createDecorator方法的实现。

node_modules/vue-class-component/lib/util.js

首先通过target获取当前组件的构造函数Ctor,判断如果参数target是一个函数,则取target,如果target是组件的实例对象,则取target.constructor

然后往构造函数的__decorators__属性中放进传入的函数factoryfactory的实现上面可以看到,就是将装饰器传入的参数options挂载在组件配置选项的props上。

export function createDecorator(factory) {
    return (target, key, index) => {
        const Ctor = typeof target === 'function'
            ? target
            : target.constructor;
        if (!Ctor.__decorators__) {
            Ctor.__decorators__ = [];
        }
        if (typeof index !== 'number') {
            index = undefined;
        }
        Ctor.__decorators__.push(options => factory(options, key, index));
    };
}
posted @ 2020-04-12 19:42  dora_zc  阅读(458)  评论(0编辑  收藏  举报