Проблема в том, чтобы заставить компилятор выводить правильный тип для этого 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"))
});