Я использовал следующий тип (подробности реализации см. в этом ответе):
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
. Я прохожу 16
. Стоит ли мне ожидать, что это сработает?
Подожди, ты хочешь сказать, что я должен написать это так: Array.from({ length: 16 as 16 }).map(() => '')
? Это тоже не работает, кроме того, что это немного глупо.
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
.
Хорошо, есть ли способ сделать синтаксис более гибким, чтобы TS фактически проверял длину массива и тип каждого используемого элемента, чтобы нам не приходилось писать ''
1000 раз для FixedSizeArray<1000, string>
? Я имею в виду без кастинга, а это неправильно. Он просто говорит ТС: «сиди и молчи, я умнее тебя, а ты в этом деле бесполезен». Кастинг сводит на нет цель использования TS, верно?
Я не знаю, что вы подразумеваете под «сделать синтаксис более гибким» — вы не можете изменить синтаксис языка, нет. Если под «приведением» вы подразумеваете утверждения типов, то они предназначены именно для тех случаев, когда вы знаете больше, чем система типов. Однако по сути вы не создаете массив фиксированной длины (и наличие length: 16
не влияет на значения в индексах до или после 15, независимо от того, разрешаете ли вы неконтролируемый доступ к индексу или нет).
Да, под «приведением» я имел в виду «утверждение типа». Мои знания TS ограничены.
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.
Вероятно, лучше иметь valueFactory
, иначе у вас возникнут проблемы с массивами изменяемых объектов.
Хорошая мысль @jonrsharpe; на самом деле я думаю, что предпочел бы просто вернуть массив без сопоставления и позволить вызывающим абонентам заполнять его по своему желанию. Я оставлю ответ как быстрый и грязный способ продемонстрировать концепцию, но правки приветствуются.
Для любого 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`
Он принимает
16
какnumber
. Он не принимает ни одинnumber
как16
(поскольку только одинnumber
,16
является одновременноnumber
и16
).Array.from({ length: 16 })
не создаетFixedSizeArray
, а просто обычный.