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)
对不起,我提出了一个新问题,我找不到解决这个问题的问题。
我在使用 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)