сцепить кортеж с элементом отдыха - PullRequest
0 голосов
/ 05 января 2019

В TypeScript 3.0 добавлено остальных элементов для типов кортежей. Однако вызов concat () для такого кортежа, похоже, теряет тип:

type MyTupleType = [number, ...string[]];
let t1: MyTupleType = [42, 'foo'];
let t2 = t1.concat('bar');  // (string | number)[]

Я могу восстановить тип, используя as MyTupleType, но я надеюсь на что-то более простое.

1 Ответ

0 голосов
/ 05 января 2019

Нет ничего проще встроенного в TypeScript. Стандартные типы библиотек для Array.prototype.concat() не заботятся о том, является ли рассматриваемый массив кортежем. Общая проблема манипулирования типами кортежей с помощью операций с массивами не легко решается в TypeScript начиная с версии 3.2. Есть некоторые операции с пропущенными типами , которые вам понадобятся для правильного выполнения.

В конкретном случае «кортежа покоя», который у вас есть, сигнатура типа для concat() довольно проста: тип вывода совпадает с типом текущего объекта (потому что добавление нуля или более элементы "хвоста" не меняют хвост). Таким образом, можно поддержать узкий вариант использования, который вы упомянули здесь.

Вы могли бы сделать это путем слияния в вашего собственного объявления, чтобы добавить перегрузку для concat(), которая вызывается, только если массив является кортежем отдыха, и вы добавляете элемент из хвоста. Возможно так:

declare global {
  type IsRestTuple<T, Y=unknown, N=never> =
    T extends Array<any> ? number extends T['length'] ?
    T[number][] extends T ? N : Y : N : N

  interface Array<T> {
    concat<Tuple extends Array<any>>(
      this: Tuple & IsRestTuple<Tuple>,
      ...items: Array<Tuple[99999999] | Array<Tuple[99999999]>>
    ): Tuple;
  }
}

Функция типа IsRestTuple<T> возвращает unknown, если T является кортежем покоя, и never в противном случае. Для этого используется набор условных типов .

Затем добавленная универсальная перегрузка использует this параметр , чтобы ограничить тип this чем-то, что проходит тест IsRestTuple (пересечение Tuple & IsRestTuple<Tuple> выводит Tuple как this. Если IsRestTuple<Tuple> равно unknown, то пересечение становится Tuple, и вывод параметра типа завершается успешно. Если это never, то пересечение становится never, и тип вывод параметров не выполняется.) Затем он принимает только параметры хвоста типа rest (я предполагаю, что 99999999-й элемент кортежа будет частью хвоста). Если выбрана эта перегрузка, результат будет таким, как вы ожидаете. Давайте попробуем это на нескольких примерах:

type MyTupleType = [number, ...string[]];
let t1: MyTupleType = [42, 'foo'];
let t2 = t1.concat('bar'); // [number, ...string[]], success
let t3 = t1.concat(100); // Array<string | number>, 100 doesn't match string
let t4 = t1.concat(false); // compile error, false doesn't match string or number
let t5: [number] = [1];
let t6 = t5.concat(1); // number[], would be [number, number] but use case unsupported

Итак, t2 - это то, что вы хотели, а остальные случаи не затрагиваются. Это самое близкое к тому, что я могу ответить на ваш вопрос. Если вы не обнаружите, что этот конкретный сценарий использования является чем-то необходимым для поддержки, он, похоже, не стоит того: он сложен и уродлив и работает только для кортежей отдыха и concat(). Используемое утверждение типа выглядит довольно привлекательно для сравнения.

В любом случае, надеюсь, это поможет; удачи!

...