3

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

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函数基本上是一个“类型转换”,它采用服务定义 (a provider) 的映射并返回一个工厂函数,即container. 服务定义接收container实例,以便他们可以查找它们的依赖关系,并返回Promise解析到服务实例的 a。

我需要关于类型声明的帮助。

我已经对name参数进行了类型检查——结果container函数只接受存在于 中的键,provider到目前为止,非常好。

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

知道怎么做吗?

4

2 回答 2

4

问题是让编译器为此推断正确的类型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,因为初始化器 for在函数体中"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"))
});
于 2018-07-04T17:20:46.433 回答
3

正如@artem 提到的,如果推断的类型以任何方式引用自身,编译器将拒绝推断对象文字的类型(这是一个彻底的限制,编译器甚至会提到这一点),所以对于当前结构,你有@artem 的答案是你会得到最好的。

您可以考虑的重构是您不一次性添加提供程序,而是使用add容器构建器类上的方法。add 的返回类型将返回一个新的容器构建器,该容器构建器可以解析在此之前添加的类型,因此新添加的服务只能使用已经注册的服务(用柠檬制作柠檬水,这可能不是完全坏的,因为它避免循环引用)

一个实现看起来像这样:

function Container() {
    type ContainerFunc<P> = <N extends keyof P>(n: N) => P[N];
    class ContainerBuilder<T extends { [P in keyof T]: Promise<any> }> {
        public constructor(private provider: { [P in keyof T]: (c: any) => T[P] } = {} as any) {

        }
        public add<K extends string, V>(name: K, p: (c: ContainerFunc<T>)=> Promise<V>): ContainerBuilder<T & { [P in K]: Promise<V> }> {
            this.provider[name] = p;
            return this as any;
        }

        finish() : ContainerFunc<{ [P in keyof T]: T[P]}> {
            const provider = this.provider;
            const cache: { [name in keyof T]?: Promise<any> } = {};
            const container = function<K extends keyof T>(name: K) : T[K]  {
                if (!cache[name]) {
                    cache[name] = provider[name](container);
                }

                return cache[name] as any;
            }
            return container as any;

        }
    }

    return new ContainerBuilder()
}

class UserCache { }
class OfficeCache {}
class UserService {
    constructor(private cache: UserCache, private officeCache?: OfficeCache) { }
}


var c = Container()
    .add("user", async ()=> new UserCache())
    // all typed corectly, no any, the comented code below would be an error as office has not been registered yet
    .add("user-service", async c=> new UserService(await c("user") /* , await c("office") */)) 
    .add("office", async c=> new OfficeCache())
.finish();

let userService = c("user-service");
于 2018-07-05T07:24:36.610 回答