typescript - 如何在 TypeScript 中为此依赖注入(inject)容器声明通用的映射类型

标签 typescript dependency-injection typescript-typings

我在 TypeScript 中想出了这个简单的异步依赖注入(inject)容器:

function Container<P extends { [name: string]: { (c: any): Promise<any> } }>(provider: P) {
    const cache: { [name in keyof P]?: Promise<any> } = {};

    const container = function(name: keyof P) {
        if (!cache[name]) {
            cache[name] = provider[name](container);
        }

        return cache[name];
    }

    return container;
}

这是一个简单的用例示例:

class UserCache { }

class UserService {
    constructor(private cache: UserCache) { }
}

let container = Container({
    "cache": async c => new UserCache(),
    "user-service": async c => new UserService(await c("cache"))
});

container("user-service").then(service => {
    console.log(service);
});

因此,简要解释一下这里的预期内容:Container 函数基本上是一个“类型转换”,它采用服务定义映射(提供者)并返回工厂函数又名 container。服务定义接收 container 实例,以便它们可以查找它们的依赖项,并返回解析为服务实例的 Promise

我需要类型声明方面的帮助。

我已经对 name 参数进行了类型检查——生成的 container 函数只接受存在于 provider 中的键,到目前为止,还不错。

缺少的是对 container 调用的通用返回类型,因此,反过来,嵌套服务查找,例如 await c("cache") 可以正确解析和类型检查。

知道怎么做吗?

最佳答案

问题是让编译器为这个container推断出正确的类型:

let container = Container({
    "cache": async c => new UserCache(),
    "user-service": async c => new UserService(await c("cache"))
});

但这取决于传递给 Container 的参数类型,它是一个对象字面量,因此编译器会查看属性初始化器的类型:

async c => new UserCache()

并且看到 c 没有类型注释,所以它的类型必须被推断出来,约束是它与 container 的类型相同 - 糟糕,TypeScript 有没有很好地进行循环约束的类型推断。

c 被推断为 any,没有办法绕过它:

// let's start with imaginary type P which maps names to provided types

// and declare desired container type
type ContainerFunc<P> = <N extends keyof P>(n: N) => Promise<P[N]>;

// and its argument type
type ProviderType<P> = { [N in keyof P]: (c: ContainerFunc<P>) => Promise<P[N]> };


function Container<P>(provider: ProviderType<P>): ContainerFunc<P> {
    const cache: { [N in keyof P]?: Promise<P[N]> } = {};

    const container = function<N extends keyof P>(name: N): Promise<P[N]> {
        if (!cache[name]) {
            cache[name] = provider[name](container);
        }

        return cache[name];
    }

    return container;
}

class UserCache { cache: {} }

class UserService {
    constructor(private cache: UserCache) { }
}

let container = Container({
    "cache": async c => new UserCache(),
    "user-service": async c => new UserService(await c("cache"))
});

要移除循环,您必须移除 c 参数 - 它始终是 container 本身,让我们这样使用它:

type ContainerFunc<P> = <N extends keyof P>(n: N) => Promise<P[N]>;

type ProviderType<P> = { [N in keyof P]: () => Promise<P[N]> };

function Container<P>(provider: ProviderType<P>): ContainerFunc<P> {
    const cache: { [N in keyof P]?: Promise<P[N]> } = {};

    const container = function<N extends keyof P>(name: N): Promise<P[N]> {
        if (!cache[name]) {
            cache[name] = provider[name]();
        }

        return cache[name];
    }

    return container;
}

class UserCache { cache: {} }

class UserService {
    constructor(private cache: UserCache) { }
}

let container = Container({
    "cache": async () => new UserCache(),
    "user-service": async () => new UserService(await container("cache"))
});

但现在 container 类型被推断为 any,因为 "user-service" 的初始化器指的是 container 在函数体中,这样可以防止推断该函数的返回类型。

你必须添加返回类型注解:

let container = Container({
    "cache": async () => new UserCache(),
    "user-service": async (): Promise<UserService> => new UserService(await container("cache"))
});

现在可以了:

let container = Container({
    "cache": async () => new UserCache(),
    "user-service": async (): Promise<UserService> => new UserService(await container("user-service"))
});

// error: Argument of type 'UserService' is not assignable to parameter of type 'UserCache'.

我相信这是你能得到的最好的。

更新 正如您在评论中所建议的那样,避免循环的另一种方法是在实现之前提前拼出所有提供者名称和提供的类型:

type ContainerFunc<P> = <N extends keyof P>(n: N) => Promise<P[N]>;

type ProviderType<P> = { [N in keyof P]: (c: ContainerFunc<P>) => Promise<P[N]> };


function Container<P>(provider: ProviderType<P>): ContainerFunc<P> {
    const cache: { [N in keyof P]?: Promise<P[N]> } = {};

    const container = function<N extends keyof P>(name: N): Promise<P[N]> {
        if (!cache[name]) {
            cache[name] = provider[name](container);
        }

        return cache[name];
    }

    return container;
}

class UserCache { cache: {} }

class UserService {
    constructor(private cache: UserCache) { }
}

interface ProviderTypes {
    cache: UserCache;
    "user-service": UserService;
}

let container = Container<ProviderTypes>({
    "cache": async c => new UserCache(),
    "user-service": async c => new UserService(await c("cache"))
});

关于typescript - 如何在 TypeScript 中为此依赖注入(inject)容器声明通用的映射类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51176441/

相关文章:

TypeScript:返回与参数相同的类型

typescript - 如何在 TypeScript 中引用同一个类中的静态变量?

node.js - 如何在 TypeScript 中动态访问对象属性

javascript - Typescript 可以声明语言特性吗?例如; `!variable` 为 `no variable`

javascript - Import'd React.Component 不使用 Typescript 渲染

typescript - 在定义中为类方法起别名

javascript - 从 firebase firestore 引用获取异步值

mysql - 在 .NET AWS Lambda 中使用 MySql.Data 和 D.I

spring - 依赖注入(inject)、Scala 和 Spring

java - Play 2.5.3 : Using dependency injection to get configuration values