我已经浏览了Aurelia DI的文档并查看了源代码并希望分享我想要实现的内容,这样如果我遗漏了一些明显的东西,我就会被击落。我查看了Aurelia的样本here,但我看不出它是如何工作的,而且缺少文档。
我想要的是:
dataProvider.js (数据提供者界面)
export interface DataProvider {
getData(): number;
}
itemDisplayer1.js (一个将使用注入的类来实现接口的类)
import {inject} from 'aurelia-framework';
import {DataProvider} from './dataProvider';
@inject(DataProvider)
export class itemDisplayer1 {
constructor(public dataProvider: DataProvider) {
this.dataProvider = dataProvider;
this.data = dataProvider.getData();
}
}
itemDisplayer2.js (另一个将使用实现接口的注入类的类)
import {inject} from 'aurelia-framework';
import {DataProvider} from './dataProvider';
@inject(DataProvider)
export class itemDisplayer2 {
constructor(public dataProvider: DataProvider) {
this.dataProvider = dataProvider;
this.data = dataProvider.getData();
}
}
GoodDataProvider.js
import {DataProvider} from "./dataProvider";
export class GoodDataProvider implements DataProvider {
data = 1;
getData() {
return this.data;
}
}
BetterDataProvider.js
import {DataProvider} from "./dataProvider";
export class BetterDataProvider implements DataProvider {
data = 2;
getData() {
return this.data;
}
}
然后在某处(?)我想配置itemDisplayer1应该提供GoodDataProvider的实例,而itemDisplayer2应该提供BetterDataProvider(1)的实例。
然后是DI上下文的问题。我不知道如何使用container.createChild()。我找不到太多关于它的信息。它创建一个子容器,它将在需要时委托给父容器,但如果我创建了2个子容器并为每个子容器注册了2个提供者之一,那么itemDisplayer类将如何知道要使用哪个(不更改其定义)并注入父容器等)?
注意:生命周期管理信息不存在于消费者或依赖关系的提供者中(这通常在Aurelia DI示例中完成,似乎有点制造)。我希望能够在消费者和提供者关联时定义这一点 - 点上面的“(1)”。
总之,这可能吗?这是近期未来的事情吗?我是否应该尝试用满足我需求的自定义容器替换Aurelia DI?
(我之所以这样做是因为为了评估js框架,框架需要演示一个成熟的DI系统,并将生命周期管理/ AOP等功能作为标准之一)
答案 0 :(得分:8)
:一旦我们写完基准,DI就会进行一些内部改革。
但是在相关的说明中,它无法使用接口,因为TypeScript会在运行时编译它们。
当您在DI容器中注册不同类型,然后在@Inject(xxx)语句中指定相应的唯一键时,您必须提供唯一键。钥匙可以是你喜欢的任何东西。通常人们会将类型本身用于唯一键(这会导致一些混淆),但您可以使用字符串,数字或其他任何您喜欢的键。
单元测试也提供了信息:https://github.com/aurelia/dependency-injection/blob/master/test/container.spec.js
答案 1 :(得分:3)
正如Mike所说,Aurelia还不支持这种依赖性解析功能。并且接口被编译掉,因此它们不能用作密钥(例如container.registerInstance(ISomething, new ConcreteSomething());
然而,有一个小技巧可以使你看起来像是将接口本身用作关键。
foo.ts:
export interface IFoo {
// interface
}
export const IFoo = Symbol();
bar.ts:
import {IFoo} from "./foo.ts";
export class Bar implements IFoo {
// implementation
}
main.ts:
import {IFoo} from "./foo.ts";
import {Bar} from "./bar.ts";
...
container.registerInstance(IFoo, new Bar());
...
编译很好,编译器根据使用它的上下文知道何时使用正确的重复类型。
答案 2 :(得分:3)
因此,正如其他人所说,TS会编译接口,目前无法通过纯接口进行此操作。然而,TS的一个有趣且经常被遗漏的特征是它允许使用class
作为接口,这使得能够解决当前的限制。
export abstract class DataProvider {
getData(): number;
}
@singleton(DataProvider) // register with an alternative key
export class MyAwesomeDataProvider implements DataProvider {
}
@autoinject
export class DataConsumer {
constructor(dataProvider: DataProvider) {
}
}
在上面的代码中,我们声明了一个抽象类DataProvider
,它将确保它不会被TS编译掉。然后,我们会向MyAwesomeDataProvider
注册key
替代DataProvider
,每次请求MyAwesomeDataProvider
时,都会返回DataProvider
个实例。
就子容器而言,您需要container.createChild()
返回容器的新实例,只要从该子容器触发解析,您就应该获得正确的实例。唯一的问题是使用具有两个冲突键的装饰器。基本上,元数据存在于类本身,因此您不能在DataProvider
下注册两个实例,这肯定会(我自己没有尝试过)导致问题,唯一的方法就是是使用显式注册。 E.g。
export abstract class DataProvider {
getData(): number;
}
export class MyAwesomeDataProvider implements DataProvider {
}
export class MyMoreAwesomeDataProvider implements DataProvider {
}
child1 = container.createChild();
child1.registerSingleton(DataProvider, MyAwesomeDataProvider);
child2 = container.createChild();
child2.registerSingleton(DataProvider, MyMoreAwesomeDataProvider);
@autoinject
export class DataConsumer {
constructor(dataProvider: DataProvider) {
}
}
child1.get(DataConsumer); // injects MyAwesomeDataProvider
child2.get(DataConsumer); // injects MyMoreAwesomeDataProvider
答案 3 :(得分:0)
喜欢 Frank Gambino 的想法,并找到了一种方法,使其与@inject
和@autoinject
一起使用。诀窍是使用自定义parameter decorator(因为接口在TypeScript中保留,我称之为@i)。
装饰者部分:
myClass.ts
import { autoinject } from 'aurelia-framework';
import { i } from './i.ts';
import { IFoo } from "./ifoo.ts";
@autoinject
export class MyClass {
constructor(@i(IFoo) foo: IFoo) {
foo.doSomething();
}
}
i.ts:
import "reflect-metadata";
/**
* Declare the interface type of a parameter.
*
* To understand more about how or why it works read here:
* https://www.typescriptlang.org/docs/handbook/decorators.html#metadata
*/
export function i(interfaceSymbol: symbol) {
return function (target: Object, parameterName: string | symbol, parameterIndex: number) {
var paramTypes = Reflect.getMetadata('design:paramtypes', target);
paramTypes[parameterIndex] = interfaceSymbol;
Reflect.defineMetadata('design:paramtypes', paramTypes, target);
}
}
其余部分与 Frank Gambino 答案完全相同,但为了完整性我添加了它 ... 强>
ifoo.ts:
export interface IFoo {
doSomething(): void;
}
export const IFoo = Symbol("IFoo"); // naming the symbol isn't mandatory, but it's easier to debug if something goes wrong
some.ts:
import { IFoo } from "./ifoo.ts";
export class Bar implements IFoo {
doSomething(): void {
console.log('it works!');
}
}
main.ts:
import { IFoo } from "./ifoo.ts";
import { Bar } from "./bar.ts";
...
container.registerInstance(IFoo, new Bar());
...
它实际上可以与其他DI容器一起使用。要使它与Angular2一起使用(虽然你为什么会这样?Aurelia更加棒极了:)你只需要将i.ts文件中interfaceSymbol
的类型改为any
而不是{{1}写Symobl("IFoo")
(InjectionToken类是一个Angular的东西,很遗憾他们不支持Symbol作为注入令牌,至少目前是这样)。
答案 4 :(得分:0)
我采用另一种方法解决了对我有用的问题。
参加以下课程:
export class Foo implements Bar {
}
我将其更改为以下内容:
import { Container } from 'aurelia-framework';
class Foo implements Bar {
}
export var foo = Container.instance.get(Foo) as Bar;
现在我可以执行以下操作以获取该类的类型化单例实例:
import { foo } from 'foo';