Я пытаюсь связать типы аргументов и возвращаемый тип функции вместе с помощью обобщений.Я уже нашел решение, но нахожу его неоптимальным, поскольку у меня есть ощущение, что это можно сделать лучше.
В принципе, у меня есть следующая функция:
function postCommand<T extends AvailableCommands>(
commandName: T,
commandArguments: ArgumentType<T>
): Observable<PayloadType<T>> {
return httpClient.post<PayloadType<T>>(`/command/${commandName}`, commandArguments);
}
в сочетании сследующие (примерные) определения:
type AvailableCommands =
| 'create-group'
| 'create-post';
type ArgumentType<T extends AvailableCommands> = T extends 'create-group'
? CreateGroupArguments
: T extends 'create-post'
? CreatePostArguments
: never;
type PayloadType<T extends AvailableCommands> = T extends 'create-group'
? CreateGroupPayload
: T extends 'create-post'
? CreatePostPayload
: never;
Примечание: *Arguments
и *Payload
определены вне области действия, а также в виде простых интерфейсов для объектов.Например, с ключами groupId
и body
для CreatePostArguments
.httpClient
также определен вне области действия (в основном это HTTP-клиент Angular7)
Приведенный выше код позволяет мне использовать функцию postCommand
с автопредставлениями для входной переменной commandName
и автопредставлениями для commandArguments
на основе которого commandName
я набрал. commandPayload
также выводится, когда я подписываюсь на возвращаемую наблюдаемую информацию.
Теперь использование условных типов уродливо и не так легко понимается другими разработчиками.
Это привело меня к попытке найти другой способ, где я наткнулся на [
. Это изменяет код следующим образом:
function postCommand<T extends AvailableCommands>(
commandName: T,
commandArguments: CommandMap[T]['arguments']
): Observable<CommandMap[T]['payload']> {
return httpClient.post<CommandMap[T]['payload']>(`/command/${commandName}`, commandArguments);
}
interface CommandMap {
'create-group': { arguments: CreateGroupArguments; payload: CreateGroupPayload };
'create-post': { arguments: CreatePostArguments; payload: CreatePostPayload };
}
Теперь, это уже выглядит более читабельным и имееттот же эффект.У меня все еще есть самовнушения.Единственное предостережение: если я опускаю create-post
в интерфейсе CommandMap
, компилятор не возражает.Это проблема обеих вышеуказанных реализаций.
Это в основном то, что я хотел, за исключением оговорки, которую я упомянул.Итак, мой вопрос сейчас таков:
Есть ли способ заставить компилятор учесть тот факт, что я не определил отображения для всех AvailableCommands
в CommandMap
, и при этом все еще имею автопредставление для аргументов и возврататип postCommand
?
(я создал Playground с моей последней версией, и имитирующую реализацию post
и версию TypeScript, которую я использую для своего проекта)
Редактировать:
Общий лучший ответ для меня был скрыт на виду:
Сейчас я в основном использую только один тип
export interface CommandMap {
[index: string]: { arguments: any; payload: any };
'create-group': { arguments: CreateGroupArguments; payload: CreateGroupPayload };
'create-post': { arguments: CreatePostArguments; payload: CreatePostPayload };
}
и вызов функции:
public postCommand<T extends keyof CommandMap>(
commandName: T,
commandArguments: CommandMap[T]['arguments']
): Observable<CommandMap[T]['payload']> {
return this.httpClient.post<CommandMap[T]['payload']>(`/command/${commandName}`, commandArguments);
}
Общий теперь расширяет keyof CommandMap
.Так как CommandMap
уже имеет все определения для моих команд, мне не потребовался дополнительный тип AvailableCommands
.
Все функции AutoSuggestion по-прежнему доступны, и ошибки находятся в нужном месте (интерфейссамо определение, если оно вообще есть)