Почему TypeScript не принимает число 16?

Я использовал следующий тип (подробности реализации см. в этом ответе):

type FixedSizeArray<N extends number, T> = {
  length: N
} & ReadonlyArray<T>

Но тогда ТС не принимает:

someProp: Array.from({ length: 16 }).map(() => '')

где someProp ожидает FixedSizeArray<16, string>.

Мне придется либо бросить это, либо написать '' 16 раз. Фактическая ошибка:

Type 'string[]' is not assignable to type 'FixedSizeArray<16, string>'.
  Type 'string[]' is not assignable to type '{ length: 16; }'.
    Types of property 'length' are incompatible.
      Type 'number' is not assignable to type '16'.ts(2322)

Это баг или я делаю что-то не так?

Он принимает 16 как number. Он не принимает ни один number как 16 (поскольку только один number, 16 является одновременно number и 16). Array.from({ length: 16 }) не создает FixedSizeArray, а просто обычный.

jonrsharpe 12.08.2024 11:29

Но я не передаю никаких цифр как 16. Я прохожу 16. Стоит ли мне ожидать, что это сработает?

tao 12.08.2024 11:34

Подожди, ты хочешь сказать, что я должен написать это так: Array.from({ length: 16 as 16 }).map(() => '')? Это тоже не работает, кроме того, что это немного глупо.

tao 12.08.2024 11:36
Type 'number' is not assignable to type '16'.ts(2322)Array.from({ length: 16 }).map(() => '') создаёт Array<string>, у которого length — это просто number. Так что это не FixedSizeArray<16, string> по той причине, о которой вам сообщает ошибка. Чтобы это работало, вам также придется переопределить сам from (см. github.com/microsoft/TypeScript/blob/…) и map.
jonrsharpe 12.08.2024 11:37

Хорошо, есть ли способ сделать синтаксис более гибким, чтобы TS фактически проверял длину массива и тип каждого используемого элемента, чтобы нам не приходилось писать '' 1000 раз для FixedSizeArray<1000, string>? Я имею в виду без кастинга, а это неправильно. Он просто говорит ТС: «сиди и молчи, я умнее тебя, а ты в этом деле бесполезен». Кастинг сводит на нет цель использования TS, верно?

tao 12.08.2024 11:39

Я не знаю, что вы подразумеваете под «сделать синтаксис более гибким» — вы не можете изменить синтаксис языка, нет. Если под «приведением» вы подразумеваете утверждения типов, то они предназначены именно для тех случаев, когда вы знаете больше, чем система типов. Однако по сути вы не создаете массив фиксированной длины (и наличие length: 16 не влияет на значения в индексах до или после 15, независимо от того, разрешаете ли вы неконтролируемый доступ к индексу или нет).

jonrsharpe 12.08.2024 11:51

Да, под «приведением» я имел в виду «утверждение типа». Мои знания TS ограничены.

tao 12.08.2024 11:57
Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой 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 для повышения производительности приложения путем загрузки модулей только тогда, когда они...
0
7
52
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

Array.length — это свойство времени выполнения с типом number. Во время выполнения это может быть любое число, не обязательно 16, как ожидалось FixedSizeArray<16, string>.length. Это то, что пытается сказать сообщение об ошибке.

Это поведение Array.length в том смысле, что его длина во время выполнения может быть любой. Включение его в FixedSizeArray — это попытка устранить эту гибкость, и в этом случае вам может потребоваться просто использовать приведение, если вы можете гарантировать, что знаете лучше, чем компилятор.

Например, используя вспомогательную функцию, например:

function createFixedSizeArray<N extends number, T>(length: N, value: T): FixedSizeArray<N, T> {
  return Array.from({ length }).map(() => value) as FixedSizeArray<N, T>;
}

...

const someProp = createFixedSizeArray(16, '');

Кастинг сводит на нет цель использования TS, верно?

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

Вы только переместили приведение в функцию. Я ищу способ, который не требует кастинга. ИМХО, кастинг противоречит цели использования TypeScript.

tao 12.08.2024 11:45

Вероятно, лучше иметь valueFactory, иначе у вас возникнут проблемы с массивами изменяемых объектов.

jonrsharpe 12.08.2024 11:45

Хорошая мысль @jonrsharpe; на самом деле я думаю, что предпочел бы просто вернуть массив без сопоставления и позволить вызывающим абонентам заполнять его по своему желанию. Я оставлю ответ как быстрый и грязный способ продемонстрировать концепцию, но правки приветствуются.

tenfour 12.08.2024 11:57

Почему ошибка?

Для любого n: number выражение Array.from({ length: n }) создает значение типа { length: number }. Затем вы пытаетесь присвоить его свойству, набранному { length: 16 }. Поскольку нет никакой гарантии, что number на самом деле является 16, вы получаете ошибку.

Так что сам вопрос некорректен. Он не пытается трактовать 16 как число, он пытается трактовать неизвестное число как 16, что, очевидно, является ошибкой.

Как мне это исправить?

В какой-то момент вам придется предоставить компилятору TypeScript определенную информацию (путем приведения или иным образом), которую он не сможет вывести самостоятельно.

Вы всегда можете определить пользовательскую функцию, которая будет выполнять приведение за вас, это не должно быть слишком сложно. В качестве альтернативы вы можете поручить самой функции Array.from автоматически определять длину, когда это возможно:

declare global {
  interface ArrayConstructor {
    from<Item, Length>(arrayLike: { [index: number]: Item; length: Length }): Item[] & { length: Length }
  }
}
Array.from({ length: 42 }).length // <-- typed as `number`
Array.from({ length: 42 as const }).length // <-- typed as `42`

Попробуйте.

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