Что означает `T расширяет только чтение неизвестно [] | []` в сигнатуре функции TypeScript `Promise.all`?

Я заметил интересную (возможно, хакерскую) часть в TypeScript lib.d.ts:

interface PromiseConstructor {
  /* ... */

  /**
   * Creates a Promise that is resolved with an array of results when all of the provided Promises
   * resolve, or rejected when any Promise is rejected.
   * @param values An array of Promises.
   * @returns A new Promise.
   */
  all<T extends readonly unknown[] | []>(values: T): Promise<{ -readonly [P in keyof T]: Awaited<T[P]>; }>;

  /* ... */
}

Все кажется нормальным, кроме той части, где T extends readonly unknown[] пишется как T extends readonly unknown[] | []. Это выглядит странно, потому что [] расширяется readonly unknown[], поэтому readonly unknown[] уже должно покрывать []. Позже я понял, что это позволяет TS определить тип кортежа:

const f = <T extends readonly unknown[] | []>(x: T): T => x;

const test_0 = f([42, "foo"]);
//    ^?: [number, string]

Без | [] это не работает:

const f = <T extends readonly unknown[]>(x: T): T => x;

const test_0 = f([42, "foo"]);
//    ^?: (number | string)[]

Соответствующий коммит GitHub, который я нашел, — вот этот.

Механизм, стоящий за этим, кажется весьма волшебным, и мне любопытно, сможет ли кто-нибудь объяснить особенности его работы.

Я не уверен, чем это будет отличаться, но <const T ... >(...) , кажется, также позволяет вывести кортеж , что кажется гораздо менее волшебным. Я предполагаю, что этот тип злоупотребил особенностью реализации до того, как были добавлены дженерики, и никогда не обновлялся. Хотя я уверен, что другие здесь дадут гораздо более глубокий ответ.

Alex Wayne 12.07.2024 19:33
См. этот комментарий к ms/TS#27179, включение кортежа в область общего ограничения (по крайней мере, с 2018 года) было подсказкой для вывода кортежей. В настоящее время есть «лучшие» способы сделать это, такие как типы кортежей с переменным числом вариантов или параметры типа const, но существующая вещь не сломана, поэтому ее не исправляют. Это полностью решает вопрос? Если да, то напишу ответ или найду подходящий источник дублирования. Если нет, то что мне не хватает?
jcalz 12.07.2024 21:31

@jcalz Это хорошее объяснение. Было бы здорово, если бы вы написали ответ. Спасибо.

Snowflyt 13.07.2024 08:56
Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой 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 для повышения производительности приложения путем загрузки модулей только тогда, когда они...
3
3
58
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

TypeScript использует различные эвристические правила, чтобы определить, как вывести подходящий тип значения, поскольку множество типов совместимы с любым заданным значением. Например, значение [42, "foo"] присваивается типам: (number | string)[]; readonly unknown[]; (42 | "foo")[]; [42, string, ...Date[]]; unknown и многие другие. Как TypeScript выбирает?

Один из способов принятия решения — посмотреть контекст, в котором появляется значение; этот контекст может «ожидать» определенного диапазона типов, который влияет на вывод. Без контекста [42, "foo"] понимается как (string | number)[], что является хорошим выбором в широком диапазоне случаев использования.

Но если [42, "foo"] появляется в контексте, который ожидает типы кортежей , то, скорее всего, будет выведен тип кортежа, подобный [string, number] (см. этот комментарий к microsoft/TypeScript#27179).

Параметр ограниченного типа , например T extends U, заставляет U фигурировать в контексте для вывода T. Итак, если T extends readonly unknown[], то в контексте нет типа кортежа и [42, "foo"] выводится как (string | number)[]. Но если T extends readonly unknown[] | [], то теперь в области T явно указан тип кортежа (хотя да, объединение[] и readonly unknown[] не содержит больше значений, чем просто readonly unknown[]), и, таким образом, выводится [number, string].


Обратите внимание, что варианты кортежей также служат контекстом кортежа, поэтому вы также можете написать

const f = <T extends unknown[]>(x: readonly [...T]) => x;    
const test_0 = f([42, "foo"]);
//    ^?: const test_0: readonly [number, string]

Предположительно, если бы типизация TypeScript для Promise.all была написана сегодня, она использовала бы вариационные кортежи вместо пустых типов кортежей. Или, может быть, он даже будет использовать параметр константного типа, чтобы явно запрашивать еще более узкие типы:

const f = <const T extends readonly unknown[]>(x: T) => x;

const test_0 = f([42, "foo"]);
//    ^?: const test_0: readonly [42, "foo"]

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

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