Значение жалобы на машинописный текст может быть неопределенным, несмотря на то, что тип не является неопределенным

У меня есть этот машинописный код:

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.

Я отредактировал так, чтобы не-минимально воспроизводимый пример даже не обсуждался, поскольку он все равно мог бы отвлекать. Заинтересованные лица всегда могут просмотреть историю изменений. Надеюсь, тебя это устраивает?

jcalz 05.04.2024 14:31

Ответ здесь: ms/TS#35257 ; TS не является средством решения доказательств и не может точно делать выводы из произвольных функций общего типа, особенно тех, которые используют условные типы. Payload<M>, где M является общим, в большинстве случаев непрозрачен для компилятора. Если вы хотите, чтобы TS знал, чем Payload<M> не может быть undefined (или null), вы можете пересечь его с {}, как показано здесь. Это полностью решает вопрос? Если да, то я напишу ответ; если нет, то что мне не хватает?

jcalz 05.04.2024 14:35

@jcalz Да, меня это устраивает, и это отвечает на мой вопрос. Спасибо.

Riccardo Perego 05.04.2024 14:37
Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой Zod и раскрыть некоторые ее особенности, например, возможности валидации и трансформации данных, а также...
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Мне нравится библиотека Mantine Component , но заставить ее работать без проблем с Remix бывает непросто.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
TypeScript против JavaScript
TypeScript против JavaScript
TypeScript vs JavaScript - в чем различия и какой из них выбрать?
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Не все нужно хранить на стороне сервера. Иногда все, что вам нужно, это постоянное хранилище на стороне клиента для хранения уникальных для клиента...
Что такое ленивая загрузка в Angular и как ее применять
Что такое ленивая загрузка в Angular и как ее применять
Ленивая загрузка - это техника, используемая в Angular для повышения производительности приложения путем загрузки модулей только тогда, когда они...
2
3
67
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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
}

Детская площадка, ссылка на код

Другие вопросы по теме