简体   繁体   English

Aurelia DI带有打字稿接口

[英]Aurelia DI with typescript interfaces

I've gone through the documentation of Aurelia DI and looked at the source code and wanted to share what I'm trying to achieve so that I can be shot down if I'm missing something obvious. 我已经浏览了Aurelia DI的文档并查看了源代码并希望分享我想要实现的内容,这样如果我遗漏了一些明显的东西,我就会被击落。 I've looked at the samples here for TS with Aurelia but I can't see how it will work, and the docs are lacking. 我已经在这里查看了Aurelia的TS示例,但我看不出它是如何工作的,而且缺少文档。

What I want is: 我想要的是:

dataProvider.js (the data provider interface) dataProvider.js (数据提供者接口)

export interface DataProvider {
  getData(): number;
}

itemDisplayer1.js (a class that will consume an injected class that implements the interface ) 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 (another class that will consume an injected class that implements the interface ) 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 GoodDataProvider.js

import {DataProvider} from "./dataProvider";

export class GoodDataProvider implements DataProvider {
  data = 1;
  getData() {
    return this.data;
  }
}

BetterDataProvider.js BetterDataProvider.js

import {DataProvider} from "./dataProvider";

export class BetterDataProvider implements DataProvider {
  data = 2;
  getData() {
    return this.data;
  }
}

And then somewhere(?) I would like to configure that itemDisplayer1 should be provided with an instance of GoodDataProvider and itemDisplayer2 should be provided with an instance of BetterDataProvider (1). 然后在某处(?)我想配置itemDisplayer1应该提供GoodDataProvider的实例,而itemDisplayer2应该提供BetterDataProvider(1)的实例。

Then comes the problem of DI context. 然后是DI上下文的问题。 I'm not sure how to use container.createChild(). 我不知道如何使用container.createChild()。 There's not much info on it that I can find. 我找不到太多关于它的信息。 It creates a child container and it will delegate back to the parent when needed, but if I create 2 child containers and register one of the 2 providers with each child container, how would the itemDisplayer classes know which one to use (without changing their definitions and injecting in the parent container etc)? 它创建一个子容器,它将在需要时委托给父容器,但如果我创建了2个子容器并为每个子容器注册了2个提供者之一,那么itemDisplayer类将如何知道要使用哪个(不更改其定义)并注入父容器等)?

Note : The lifetime management information doesn't live in the consumers or the providers of the dependencies (this is often done in the Aurelia DI examples and seems a little manufactured). 注意 :生命周期管理信息不存在于消费者或依赖关系的提供者中(这通常在Aurelia DI示例中完成,并且似乎有点制造)。 I would expect this to be able to be defined when the consumers and providers are associated - point '(1)' above. 我希望能够在消费者和提供者关联时定义这一点 - 在上面指出“(1)”。

In summary, is this possible? 总之,这可能吗? Is this something that is on the cards for the near-ish future? 这是近期未来的事情吗? Should I be trying to replace Aurelia DI with a custom container that meets my needs? 我是否应该尝试用满足我需求的定制容器替换Aurelia DI?

(The reason I'm trying to do this is that in order to evaluate js frameworks, the frameworks need to demonstrate a mature DI system with lifetime management/AOP etc capabilities as one of the criteria) (我试图这样做的原因是,为了评估js框架,框架需要演示一个成熟的DI系统,其中包含生命周期管理/ AOP等功能作为标准之一)

from @eisenbergeffect: The DI is going to get some internal overhaul once we get the benchmarks written. 来自@eisenbergeffect:一旦我们得到基准,DI就会得到一些内部改革。

But on a related note, it can't work with interfaces because TypeScript compiles those away at runtime. 但是在相关的说明中,它不能用于接口,因为TypeScript在运行时编译它们。

You would have to come up with unique keys when you register your different types in the DI container and then specify the appropriate unique key in the @Inject(xxx) statement. 在DI容器中注册不同类型时,必须提供唯一键,然后在@Inject(xxx)语句中指定相应的唯一键。 The keys can be anything you like. 钥匙可以是你喜欢的任何东西。 Normally folks use the type itself for the unique key (this causes some confusion), but you could use strings, numbers, or anything else you like. 通常人们会将类型本身用于唯一键(这会导致一些混淆),但您可以使用字符串,数字或其他任何您喜欢的内容。

the unit tests are informative also: https://github.com/aurelia/dependency-injection/blob/master/test/container.spec.js 单元测试也提供信息: https//github.com/aurelia/dependency-injection/blob/master/test/container.spec.js

As Mike said, Aurelia doesn't support this dependency resolving feature yet. 正如迈克所说,Aurelia还不支持这种依赖性解析功能。 And interfaces get compiled away, so they cannot be used as keys (eg container.registerInstance(ISomething, new ConcreteSomething()); 并且接口被编译掉,所以它们不能用作键(例如container.registerInstance(ISomething, new ConcreteSomething());

However, there is a small trick that can make it look like you're using the interface itself as the key. 但是,有一个小技巧可以使它看起来像你正在使用接口本身作为关键。

foo.ts: foo.ts:

export interface IFoo {
  // interface
}

export const IFoo = Symbol();

bar.ts: bar.ts:

import {IFoo} from "./foo.ts";

export class Bar implements IFoo {
  // implementation
}

main.ts: main.ts:

import {IFoo} from "./foo.ts";
import {Bar} from "./bar.ts";

...

container.registerInstance(IFoo, new Bar());

...

This compiles fine, and the compiler knows when to use the correct duplicate type based on the context in which it is used. 这编译得很好,编译器知道何时根据使用它的上下文使用正确的重复类型。

So, as stated by others, TS compiles the interfaces aways and there is currently no way of doing this with pure interfaces. 因此,正如其他人所说,TS编译接口,目前无法用纯接口进行此操作。 However, an interesting and often missed feature of TS is that it allows using class as an interface, this enables working around the current limitation. 然而,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) {
  }
}

In the above code, we declare an abstract class DataProvider which will ensure that it's not compiled away by TS. 在上面的代码中,我们声明了一个抽象类DataProvider ,它将确保它不会被TS编译掉。 We then register MyAwesomeDataProvider with an alternative key of DataProvider , which will return an instance of MyAwesomeDataProvider every time a DataProvider is requested. 然后,我们注册MyAwesomeDataProvider一个替代keyDataProvider ,将返回的实例MyAwesomeDataProvider每一次DataProvider请求。

As far as child containers go, you'd do container.createChild() which returns a new instance of the container and as long as the resolution is triggered from that child container, you should get the correct instance. 就子容器而言,你会做container.createChild() ,它返回container.createChild()一个新实例,只要从该子容器触发解析,你就应该得到正确的实例。 The only problem is using decorators with two conflicting keys. 唯一的问题是使用具有两个冲突键的装饰器。 Basically, the metadata lives on the class itself, so you can't have two instances registering under DataProvider , that would surely (tho I haven't tried it out myself) cause issues, the only way to go about it is use explicit registration. 基本上,元数据存在于类本身,因此您不能在DataProvider下注册两个实例,这肯定会(我自己没有尝试过)导致问题,唯一的方法是使用显式注册。 Eg 例如

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

Loved the idea of Frank Gambino and found a way to make it work both with @inject and @autoinject . 喜欢Frank Gambino的想法,并找到了一种方法,使其与@inject@autoinject The trick is to use a custom parameter decorator (since interface is reserved in TypeScript I called it @i). 诀窍是使用自定义参数装饰器 (因为接口在TypeScript中保留,我称之为@i)。

The decorator parts: 装饰部分:

myClass.ts 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);
    }
}

The rest of it is exactly like Frank Gambino answer but I added it for completeness ... 剩下的就像弗兰克甘比诺的回答一样,但为了完整性我添加了它...

ifoo.ts: 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: some.ts:

import { IFoo } from "./ifoo.ts";

export class Bar implements IFoo {
    doSomething(): void {
        console.log('it works!');
    }
}

main.ts: main.ts:

import { IFoo } from "./ifoo.ts";
import { Bar } from "./bar.ts";

...

container.registerInstance(IFoo, new Bar());

...

And it can actually work with other DI containers. 它实际上可以与其他DI容器一起使用。 To make it work with Angular2 (although why would you? Aurelia is much more awesome :) you just need to change the type of interfaceSymbol in the i.ts file to any and instead of Symobl("IFoo") write new InjectionToken("IFoo") (the InjectionToken class is an Angular thingy and sadly enough they don't support Symbol as an injection token, at least for the time being). 为了使它与Angular2一起工作(虽然你为什么会这样?Aurelia更加棒极了:)你只需要将i.ts文件中的interfaceSymbol类型更改为any而不是Symobl("IFoo")写入new InjectionToken("IFoo") (InjectionToken类是一个Angular的东西,很遗憾他们不支持Symbol作为注入令牌,至少目前是这样)。

I had a different approach to solving this that worked for me. 我有一个不同的方法来解决这个对我有用的方法。

Take the following class: 参加以下课程:

export class Foo implements Bar {

}

I changed this to the following: 我将此更改为以下内容:

import { Container } from 'aurelia-framework';

class Foo implements Bar {
}

export var foo = Container.instance.get(Foo) as Bar;

Now I can just do the following to get a typed singleton instance of the class: 现在我可以执行以下操作来获取该类的类型化单例实例:

import { foo } from 'foo';

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM