Babel - decorated class 属性的装饰器在实例化 class 之前被调用

Babel - decorator of decorated class properties is called before instantiating class

对不起,我提出了一个新问题,我找不到解决这个问题的问题。

我在使用 mocha 和使用 babel 转译的实验性 es6+ 装饰器测试我的依赖注入时遇到困难。 class 属性 装饰器在它应该被调用之前就被调用了。

injection.test.js(摩卡测试,使用--require babel-register

import * as DependencyInjection from '../build/decorators/DependencyInjection';

@DependencyInjection.Injectable(service => service.injected = true)
class SampleService {

    property = 'default';

    constructor(property, ...data) {
        this.property = property || this.property;
    }
}

class Dependant {

    /** @type {SampleService} */
    @DependencyInjection.Inject(SampleService)
    sampleService;
}

describe('dependency injection', () => {

    describe('is decoratored as injectable', () => {
        it('should be injectable', done => done(SampleService.injectable ? void 0 : new Error('Injectable is not set')));

        it('should contain a predicate', done => done(SampleService.predicate ? void 0 : new Error('Predicate is not set')));
    });

    describe('inject injectable', () => {
        it ('should inject the injectable provider', done => {
            const dependant = new Dependant();

            done(!!dependant.sampleService ? void 0 : new Error('Injectable provider was not injected'));
        })
    });
});

当运行测试时,修饰的class被转化为inteded。但是,在第二个测试中创建的 Dependant 实例的 sampleService 属性 是未定义的。

一旦 class 的实例被创建,有问题的装饰器应该是 called/invoked,但是当 class 被定义并且 属性 装饰。使用 TypeScript.

时会保持预期的行为

下面我列出了(简化的)装饰器和我的 babel 配置。

.babelrc

{
    "presets": [
        "env",
        "stage-0",
        "es2017"
    ],
    "plugins": [
        "syntax-decorators",
        "transform-decorators-legacy",
        ["transform-runtime", { 
            "polyfill": false,
            "regenerator": true
        }]
    ]
}

导出装饰器注入(目标class property):

exports.Inject = (typeFunction, ...data) => {
    return function (target, propertyName) {
        try {
            const injected = target[propertyName] = new typeFunction(data);
            if ('predicate' in typeFunction && typeof typeFunction.predicate === 'function') {
                typeFunction.predicate(injected);
            }
        }
        catch (err) {
            throw new Error(err.message || err);
        }
    };
};

导出装饰器 Injectable(目标 class):

exports.Injectable = (predicate) => {
    return function (target) {
        const provider = target;
        provider.injectable = true;
        if (predicate && typeof predicate === 'function') {
            provider.predicate = predicate;
        }
    };
};

我仍然没有找到它在装饰 class 属性 时创建 class 的新实例的主要原因.但是,我找到了解决问题的有效方法。在 mocha 中,使用 --require babel-register 和遗留装饰器插件,class 属性 装饰器使用 PropertyDescriptor。将简化的 Inject 装饰器更改为以下内容解决了我未实例化装饰的 class 属性.

的问题
exports.Inject = (typeFunction, ...data) => {
    return function (target, propertyName, descriptor) {
        let value = null;
        try {
            const injected = value = target[propertyName] = new typeFunction(...data);
            if ('predicate' in typeFunction && typeof typeFunction.predicate === 'function') {
                typeFunction.predicate(injected);
            }
        }
        catch (err) {
            throw new Error(err.message || err);
        }
        if (descriptor) {
            delete descriptor.initializer;
            delete descriptor.writable;
            descriptor.value = value;
        }
    };
};

删除 writable 属性 是必要的。

以下测试...

const assert = require('assert');
const chai = require('chai');

import * as DependencyInjection from '../build/decorators/DependencyInjection';

@DependencyInjection.Injectable(service => service.injected = true)
class SampleService {

    property = 'default';

    constructor(property, ...data) {
        this.property = property || this.property;
    }
}

class Dependant {

    /** @type {SampleService} */
    @DependencyInjection.Inject(SampleService)
    sampleService;
}

class Dependant2 {

    /** @type {SampleService} */
    @DependencyInjection.Inject(SampleService, 'overloaded')
    sampleService;
}

describe('dependency injection', () => {

    describe('is decoratored as injectable', () => {
        it('should be injectable', done => done(SampleService.injectable ? void 0 : new Error('Injectable is not set')));

        it('should contain a predicate', done => done(SampleService.predicate ? void 0 : new Error('Predicate is not set')));
    });

    describe('inject at decorated class property', () => {
        it('should inject the injectable provider at the decorated property', () => {
            const dependant = new Dependant();

            chai.expect(dependant.sampleService).to.be.instanceof(SampleService, 'Injectable provider was not injected');

            chai.assert.isTrue(dependant.sampleService.injected, 'The predicate of the injectable service was not set');

            chai.expect(dependant.sampleService.property).to.equal('default', 'The initial value of \'property\' was not \'default\'');
        });

        it('should inject the injectable provider with overloaded constructor arguments at the decorated property', () => {
            const dependant = new Dependant2();

            chai.expect(dependant.sampleService).to.be.instanceOf(SampleService, 'Injectable provider was not injected');

            chai.assert.isTrue(dependant.sampleService.injected, 'The predicate of the injectable service was not set');

            chai.expect(dependant.sampleService.property).to.equal('overloaded', 'The value of \'property\' was not overloaded');
        });
    });

    describe('inject at manual target and property', () => {
        it('should inject the injectable provider at the targeting value', () => {
            const inject = DependencyInjection.Inject(SampleService);

            const target = {};

            let err = null;
            try {
                inject(target, 'service');
            } catch (e) {
                err = e;
            }

            chai.assert.isNull(err, 'Expected injection to pass');

            chai.expect(target.service).to.be.instanceOf(SampleService, 'Injectable provider was not injected');

            chai.assert.isTrue(target.service.injected, 'The predicate of the injectable service was not set');

            chai.expect(target.service.property).to.equal('default', 'The initial value of \'property\' was not \'default\'');
        });

        it('should inject the injectable provider with overloaded constructor arguments at the targeting value', () => {
            const inject = DependencyInjection.Inject(SampleService, 'overloaded');

            const target = {};

            let err = null;
            try {
                inject(target, 'service');
            } catch (e) {
                err = e;
            }

            chai.assert.isNull(err, 'Expected injection to pass');

            chai.expect(target.service).to.be.instanceOf(SampleService, 'Injectable provider was not injected');

            chai.assert.isTrue(target.service.injected, 'The predicate of the injectable service was not set');

            chai.expect(target.service.property).to.equal('overloaded', 'The value of \'property\' was not overloaded');
        });

        it('should not inject anything at the targeting value', () => {
            const inject = DependencyInjection.Inject();

            const target = {};

            let err = null;
            try {
                inject(target, 'service');
            } catch (e) {
                err = e;
            }

            chai.expect(err).to.be.instanceof(Error);

            chai.assert.notExists(target.service);
        });
    });
});

...输出如下结果:

  dependency injection
    is decoratored as injectable
      √ should be injectable
      √ should contain a predicate
    inject at decorated class property
      √ should inject the injectable provider at the decorated property
      √ should inject the injectable provider with overloaded constructor arguments at the decorated property
    inject at manual target and property
      √ should inject the injectable provider at the targeting value
      √ should inject the injectable provider with overloaded constructor arguments at the targeting value
      √ should not inject anything at the targeting value


  7 passing (29ms)