我在 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/