javascript - Typdefinition för array med minsta och största längd i Typescript
Hur kan jag skapa en typdefinition för en array med minimilängd och maximilängd, som i:
var my_arr:my_type<type, {start: 1, end: 3}>;
my_arr = [1] - pass
my_arr = [1,2] - pass
my_arr = [1,2,3] - pass
my_arr = [1,2,3,4] - fail
my_arr = [1, "string"] - fail
D.v.s. ange typen på elementen i arrayen och ett startindex och slutindex av längd så att om längden är mindre än startindexet misslyckas det, eller om längden är större än slutindexet misslyckas det.
Jag har hittat denna lösning för en array med fast längd på nätet:
type SingleLengthArray<
T,
N extends number,
R extends readonly T[] = []
> = R["length"] extends N ? R : SingleLengthArray<T, N, readonly [T, ...R]>;
Och jag kunde skapa en typ som fungerar om längden på arrayen antingen är lika med startindexet eller slutindexet men inget däremellan:
type MultipleLengthArray<
T,
N extends { start: number; end: number },
R extends readonly T[] = []
> = SingleLengthArray<T, N["start"], R> | SingleLengthArray<T, N["end"], R>;
Vad jag tror skulle fungera om det är möjligt är att skapa en array av nummer från startindexet upp till slutindexet (dvs. startindex=1, slutindex=5 => arrayen skulle vara [1,2,3,4,5]) och iterera över arrayen och så
type MultipleLengthArray<T, N extends {start: number, end:number}, R extends readonly T[] = []> = for each value n in array => SingleLengthArray<T, n1, R> | SingleLengthArray<T, n2, R> | ... | SingleLengthArray<T, n_last, R>
Vänligen låt mig veta om det finns ett sätt att göra detta, tack.
Svar #1
Jag tolkar detta som att du letar efter en typfunktion som vi kan kalla TupMinMax<T, Min, Max> som löser till en tupltyp där varje element är av typen T, och vars längd måste vara mellan Min och Max inkluderat. Vi kan representera detta som en enda tupltyp med valfria element på varje index större än Min och mindre än eller lika med Max. (Om du inte aktiverar kompilatoralternativet --exactOptionalPropertyTypes, kommer detta också att tillåta undefined-värden för dessa valfria egenskaper, men jag antar att det inte är ett stort problem). Så du vill till exempel att TupMinMax<number, 1, 3> ska vara [number, number?, number?].
Här är ett tillvägagångssätt:
type TupMinMax<
T, Min extends number, Max extends number,
A extends (T | undefined)[] = [], O extends boolean = false
> = O extends false ? (
Min extends A['length'] ? TupMinMax<T, Min, Max, A, true> :
TupMinMax<T, Min, Max, [...A, T], false>
) : Max extends A['length'] ? A :
TupMinMax<T, Min, Max, [...A, T?], false>;
Detta är en tail-rekursiv villkorlig typ, där TupMinMax<T, Min, Max> har några extra parametrar A och O för att fungera som ackumulatorer för att lagra mellanliggande tillstånd. Specifikt kommer A att lagra tuplens resultat hittills, medan O kommer att vara antingen true eller false för att representera om vi har gått in i den valfria delen av tupeln. Den börjar som false och blir true senare.
Den första villkorskontrollen är O extends false ? (...) : (...). Om O är false har vi ännu inte nått minsta längd och elementen bör vara obligatoriska. Sedan kontrollerar vi Min extends A['length'] för att se om ackumulatorn har nått minsta längd än. Om så är fallet byter vi omedelbart O till true med samma A ackumulator. Om inte, lägger vi till ett obligatoriskt T-element i slutet av A. Om O inte är false är det true och vi kontrollerar sedan Max extends A['length'] för att se om ackumulatorn har nått maximal längd än. Om så är fallet är vi klara och utvärderar till A. Om inte, lägger vi till ett valfritt T-element i slutet av A.
Låt oss prova det:
type OneTwoOrThreeNumbers = TupMinMax<number, 1, 3>;
// type OneTwoOrThreeNumbers = [number, number?, number?]
let nums: OneTwoOrThreeNumbers;
nums = []; // error
nums = [1]; // okay
nums = [1, 2]; // okay
nums = [1, 2, 3]; // okay
nums = [1, 2, 3, 4]; // error
nums = [1, "two", 3]; // error
type BetweenTenAndThirtyStrings = TupMinMax<string, 10, 30>;
/* type BetweenTenAndThirtyStrings = [string, string, string, string,
string, string, string, string, string, string, string?, string?,
string?, string?, ... 15 more ...?, string?] */
let strs: BetweenTenAndThirtyStrings;
strs = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14"]
Ser bra ut. Den svansrekursiva funktionen betyder att kompilatorn bör kunna hantera ett rekursionsdjup på cirka 1 000 nivåer, så om din tupellängdsintervall är så stort som flera hundra bör det kompilera utan problem.
Observera att sådana rekursiva typer kan vara känsliga och utsatta för besvärliga specialfall. Om du gillar att tortera dig själv och din kompilator kan du försöka skicka något annat än icke-negativa heltal som Min och Max, eller skicka in ett Min som är större än Max. Rekursionens basfall kommer aldrig att nås och kompilatorn kommer, om du har tur, att klaga på rekursionsdjupet; och om du inte har tur, kommer den att använda mycket CPU och göra din dator varm:
// 🔥💻🔥 followed by
// Type instantiation is excessively deep and possibly infinite.
// type Oops = TupMinMax<any, 10, 1>;
// type Oops2 = TupMinMax<any, 3.5 4>;
Så var försiktig.
Lekplatslänk till kod
Svar #2
Du kan kanske utnyttja en Tuples med valfria egenskaper. Tuples har en specificerad längd (vilket möjliggör valfriheten hos dess egenskaper).
enum E { V, A, S }
type TupleOfE = [ E, E, E, E?, E? ]
const e: TupleOfE = [ E.V, E.A, E.S, E.S, E.S ]
console.log(e)
console.log(e.filter(e => e === E.S))
