У меня есть этот машинописный код:
type ApiMethods = {
getToken(args: { tokenAddress: string; tokenName: string }): string;
getRank(): string;
getName(): string;
};
type Payload<K extends keyof ApiMethods> = Parameters<
ApiMethods[K]
>[0] extends undefined
? {}
: Parameters<ApiMethods[K]>[0];
function buildUrl<M extends keyof ApiMethods>(
payload: Payload<M>,
) {
let keys = Object.keys(payload)
}
Object.keys(payload)
в последней функции выдает следующую ошибку:
No overload matches this call.
Overload 1 of 2, '(o: {}): string[]', gave the following error.
Argument of type '{} | { tokenAddress: `0x${string}`; tokenName: string; } | undefined' is not assignable to parameter of type '{}'.
Я не понимаю, в каком случае Payload
может быть undefined
, поскольку оно установлено в Parameters<ApiMethods[K]>[0]
только тогда, когда это не undefined
, а когда оно есть, оно установлено в пустое object
{}
.
Я знаю, что могу это исправить, выполнив Object.keys(payload!)
, но мне было интересно, почему тип явно не сообщает TS, что этого не может быть undefined
.
Ответ здесь: ms/TS#35257 ; TS не является средством решения доказательств и не может точно делать выводы из произвольных функций общего типа, особенно тех, которые используют условные типы. Payload<M>
, где M
является общим, в большинстве случаев непрозрачен для компилятора. Если вы хотите, чтобы TS знал, чем Payload<M>
не может быть undefined
(или null
), вы можете пересечь его с {}
, как показано здесь. Это полностью решает вопрос? Если да, то я напишу ответ; если нет, то что мне не хватает?
@jcalz Да, меня это устраивает, и это отвечает на мой вопрос. Спасибо.
TypeScript не обладает способностью рассуждения более высокого порядка самостоятельно выводить инварианты из общих типов. Особенно это плохо для условных типов , которые в принципе могут иметь всякое безумное поведение. Компилятор не может сказать: «хм, независимо от того, что такое K
, Payload<K>
будет ненулевым». Для этого ему придется либо перебрать все возможные типы, которые K
могут быть, и проверить каждый из них (что сложно реализовать, см. это сообщение в Твиттере о неполноте со стороны руководителя команды разработчиков TS ), или каким-то образом способен отслеживать каждое логическое следствие структуры Payload
(что также было бы трудноразрешимо). Единственные выводы более высокого порядка, которые TypeScript может сделать об общих типах, — это те, для поиска которых он явно реализован. В противном случае он, по сути, сдастся и не позволит вам выполнять задания, даже если они окажутся безопасными. См. microsoft/TypeScript#35257 для получения дополнительной информации.
Всегда будут ситуации, когда вы знаете (или, по крайней мере, верите), что тип X
можно присвоить типу Y
, но компилятор с этим не согласен, поскольку либо X
, либо Y
зависят от какой-то общей вещи, которую компилятор не может проанализировать. В таких ситуациях вы часто можете обойти проблему, используя пересечениеX & Y
вместо X
. Одно из правил, которые знает компилятор, заключается в том, что X & Y
всегда можно назначить как X
, так и Y
. И если вы правы, что X
можно присвоить Y
, то тип X & Y
должен быть эквивалентен X
(потому что, если каждое значение типа X
можно присвоить Y
, то X & Y
должно включать в себя все X
. Оказывается, это немного более тонкий, чем этот, и есть альтернативы, такие как Extract<Y, X>
или Extract<X, Y>
, в зависимости от варианта использования, но я не буду здесь отвлекаться дальше).
Поскольку Object.keys() хочет, чтобы ее аргумент был ненулевым (например, можно назначать {}
), вы можете переопределить Payload<K>
так, чтобы он был
type Payload<K extends keyof ApiMethods> = (
Parameters<ApiMethods[K]>[0] extends undefined ? {} : Parameters<ApiMethods[K]>[0]
) & {};
и тогда это просто сработает:
type X = Payload<"getName">; // type X = {}
type Y = Payload<"getToken">; // type Y = { tokenAddress: EthereumAddress; tokenName: string; }
function buildUrl<M extends keyof ApiMethods>(
payload: Payload<M>,
) {
let keys = Object.keys(payload) // okay
}
Я отредактировал так, чтобы не-минимально воспроизводимый пример даже не обсуждался, поскольку он все равно мог бы отвлекать. Заинтересованные лица всегда могут просмотреть историю изменений. Надеюсь, тебя это устраивает?