【问题标题】:Aurelia DI with typescript interfaces带有打字稿接口的 Aurelia DI
【发布时间】:2018-09-20 18:41:58
【问题描述】:

我浏览了 Aurelia DI 的文档并查看了源代码,并想分享我正在努力实现的目标,这样如果我遗漏了一些明显的东西,我就可以被击落。我已经查看了带有 Aurelia 的 TS 的示例 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等能力作为标准之一)

【问题讨论】:

  • 来自 gitter 频道的评论:但在相关说明中,它不能与接口一起使用,因为 TypeScript 在运行时将它们编译掉。

标签: javascript dependency-injection aurelia


【解决方案1】:

来自@eisenbergeffect:一旦我们编写了基准测试,DI 将会进行一些内部检修。

但在相关说明中,它不能与接口一起使用,因为 TypeScript 在运行时将它们编译掉。

当您在 DI 容器中注册不同类型时,您必须提供唯一键,然后在 @Inject(xxx) 语句中指定适当的唯一键。钥匙可以是任何你喜欢的。通常人们使用类型本身作为唯一键(这会导致一些混乱),但您可以使用字符串、数字或任何其他您喜欢的东西。

单元测试也很丰富:https://github.com/aurelia/dependency-injection/blob/master/test/container.spec.js

【讨论】:

  • 感谢您回复我。很高兴知道大修 - 知道什么时候会(大致)?我应该知道被编译掉的 TS 接口 - 有道理 - 但这个想法是成立的。例如,di-ts 使用类而不是接口,这大概是一种软糖。
  • 是的 - 完全正确。在这个例子中(github.com/VaclavObornik/di-ts#maints),类被用作注入器容器中的键。 MockEngine让容器知道可以代替Engine使用,然后MockEngine注册到容器中。
【解决方案2】:

因此,正如其他人所说,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 注册为DataProvider 的替代key,这将在每次请求DataProvider 时返回MyAwesomeDataProvider 的实例。

就子容器而言,您会执行container.createChild(),它返回容器的新实例,只要从该子容器触发解析,您就应该获得正确的实例。唯一的问题是使用具有两个冲突键的装饰器。基本上,元数据存在于类本身上,因此您不能在DataProvider 下注册两个实例,这肯定会(虽然我自己没有尝试过)会导致问题,唯一的解决方法是使用显式注册。例如

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

【讨论】:

  • true.. 但始终注意编译后的 JavaScript 代码会因接口使用而异.. ..即接口 IFoo {} 类 Foo 实现 IFoo {}
  • true.. 但始终注意编译后的 JavaScript 代码会因接口使用而异.. ..即interface IFoo {} class Foo 实现 IFoo {} 编译成 class Foo {} 和..... export abstract class IBoo {} class Boo 实现 IBoo {} 编译成... define(["require", "exports"] , function (require, exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); class IBoo { } exports.IBoo = IBoo; class Boo { } });是什么给了你不同数量的代码......
【解决方案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());

...

这样可以很好地编译,并且编译器会根据使用它的上下文知道何时使用正确的重复类型。

【讨论】:

  • 我刚试过这个技巧,但它不起作用。我正在使用 tsc v2.0.3
  • 编译器会发出任何错误吗?它在电子应用程序中的 2.0.0-2.0.3 上运行良好。其他 JS 引擎可能会将空对象视为相同的引用。
  • 我只能说,使用 TypeScript 2.1 和最新版本的 Aurelia 容器,它仍然适用于我。
  • 我相信 2.1 处于测试阶段,但还可以
  • const IFoo = Symbol() 的工作方式是否相同或更好?
【解决方案4】:

喜欢 Frank Gambino 的想法,并找到了一种方法使其同时适用于 @inject@autoinject。诀窍是使用自定义的parameter decorator(因为 interface 在 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

一些.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 而不是 Symobl("IFoo")new InjectionToken("IFoo") (InjectionToken 类是一个 Angular 的东西,可悲的是,它们不支持 Symbol 作为注入令牌,至少目前是这样)。

【讨论】:

    【解决方案5】:

    我有一种不同的方法来解决这个问题。

    参加以下课程:

    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';
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-12-12
      • 1970-01-01
      • 2019-09-28
      • 1970-01-01
      • 2017-09-02
      • 1970-01-01
      • 1970-01-01
      • 2017-03-06
      相关资源
      最近更新 更多