Typescript tip stražar za zahtijevanje svih elemenata
Postoji li način da se zahtijevaju elementi niza s TypeScriptom tako da mogu imati
type E = keyof T; // Number of properties in T is unknown
Neka je T definiran kao u ovom primjeru:
interface T{
el1:number,
el2:number,
el3:number
}
Dobiveni typeguard trebao bi provjeriti da su sve i samo svojstva T izložena u rezultirajućem nizu. Na primjer, koristeći primjer T od gore:
[{"arg":"el1"},{"arg":"el2"},{"arg":"el3"}] //only correct option
[{"arg":"el1"},{"arg":"el2"}] // should fail
[{"arg":"el1"},{"arg":"el2"},{"arg":"el2"},{"arg":"el3"}] // should fail
[{"arg":"el1"},{"arg":"el2"},{"arg":"el8"}] // should fail
Trenutno koristim
type ITestElements = {
fn: E
}[];
što također pokriva samo drugi primjer kao pozitivan.
Odgovor #1
Definirat ću ovo:
type Arg<T> = T extends any ? { arg: T } : never;
tako da možemo koristiti Arg<E> (ekvivalentno {arg:"el1"}|{arg:"el2"}|{arg:"el3"}) u onome što slijedi.
Najbolje što ovdje možete očekivati je neka generička pomoćna funkcija verifyArray() koja bi provodila ograničenja da je njezin argument:
- niz elemenata iz unije
- ne nedostaje nijedan element iz unije
- i koji ne sadrži duplikate
I bit će ružno.
Ne postoji upotrebljiva konkretna vrsta koja će to osigurati za unije koje sadrže više od otprilike šest elemenata. Moguće je koristiti neke nezakonito rekurzivne ili legalno nerekurzivne, ali zamorne definicije tipova za uzimanje unije tipa poput 0 | 1 | 2 | 3 i pretvaranje u uniju svih mogućih tupla koji zadovoljavaju vaše kriterije. To bi proizvelo nešto poput
type AllTuples0123 = UnionToAllPossibleTuples<0 | 1 | 2 | 3>
što bi bilo ekvivalentno
type AllTuples0123 =
| [0, 1, 2, 3] | [0, 1, 3, 2] | [0, 2, 1, 3] | [0, 2, 3, 1] | [0, 3, 1, 2] | [0, 3, 2, 1]
| [1, 0, 2, 3] | [1, 0, 3, 2] | [1, 2, 0, 3] | [1, 2, 3, 0] | [1, 3, 0, 2] | [1, 3, 2, 0]
| [2, 0, 1, 3] | [2, 0, 3, 1] | [2, 1, 0, 3] | [2, 1, 3, 0] | [2, 3, 0, 1] | [2, 3, 1, 0]
| [3, 0, 1, 2] | [3, 0, 2, 1] | [3, 1, 0, 2] | [3, 1, 2, 0] | [3, 2, 0, 1] | [3, 2, 1, 0]
Ali za ulazni uniju od n elemenata to bi proizvelo izlaznu uniju od n! (to je n faktorial) izlaza, što vrlo brzo raste s n. Za vaš primjer "el1"|"el2"|"el3" to bi bilo u redu:
type AllPossibleTuplesOfArgs = UnionToAllPossibleTuples<Arg<E>>;
const okay: AllPossibleTuplesOfArgs =
[{ "arg": "el1" }, { "arg": "el2" }, { "arg": "el3" }]; // okay
const bad1: AllPossibleTuplesOfArgs = [{ "arg": "el1" }, { "arg": "el2" }]; // error!
const bad2: AllPossibleTuplesOfArgs = // error!
[{ "arg": "el1" }, { "arg": "el2" }, { "arg": "el2" }, { "arg": "el3" }];
const bad3: AllPossibleTuplesOfArgs =
[{ "arg": "el1" }, { "arg": "el2" }, { "arg": "el8" }] // error!
ali pretpostavljam da želite nešto što neće srušiti vaš prevodilac kada vaš objekt ima sedam ili više svojstava. Dakle, odustajemo od UnionToAllPossibleTuples i bilo kojeg konkretnog tipa.
Pa kako bi verifyArray() izgledao?
Prvo napravimo tip funkcije nazvan NoRepeats<T> koja prima tip torka T i vraća istu stvar kao T ako i samo ako T nema ponovljenih elemenata... inače vraća modificirani tork koji se ne može dodijeliti T. Ovo će nam omogućiti da napravimo ograničenje T extends NoRepeats<T> kako bismo rekli "tip torka T nema ponovljenih elemenata". Evo jednog načina kako to napraviti:
type NoRepeats<T extends readonly any[]> = { [M in keyof T]: { [N in keyof T]:
N extends M ? never : T[M] extends T[N] ? unknown : never
}[number] extends never ? T[M] : never }
Dakle, NoRepeats<[0,1,2]> je [0,1,2], ali NoRepeats<[0,1,1]> je [0,nikad,nikad]. Zatim verifyArray() može biti napisan ovako:
const verifyArray = <T>() => <U extends NoRepeats<U> & readonly T[]>(
u: (U | [never]) & ([T] extends [U[number]] ? unknown : never)
) => u;
Potrebno je tip T za provjeru, i vraća novu funkciju koja osigurava da njen argument nema ponavljanja (iz U extends NoRepeats<U>), da se može dodijeliti T[] (iz & readonly T), i da ne nedostaje nijedan element iz T (iz & ([T] extends [U[number]] ? unknown : never)). Da, ružno je. Pogledajmo radi li:
const verifyArgEArray = verifyArray<Arg<E>>()
const okayGeneric = verifyArgEArray([{ "arg": "el3" }, { "arg": "el1" }, { "arg": "el2" }]); // okay
const bad1Generic = verifyArgEArray([{ "arg": "el1" }, { "arg": "el2" }]); // error
const bad2Generic = // error
verifyArgEArray([{ "arg": "el1" }, { "arg": "el2" }, { "arg": "el2" }, { "arg": "el3" }]);
const bad3Generic = // error
verifyArgEArray([{ "arg": "el1" }, { "arg": "el2" }, { "arg": "el8" }]);
Tako da to funkcionira.
Oba vas prisiljavaju da se borite s tipovima. Mogli biste eventualno napraviti klasu graditelja kao u ovom odgovoru koja se bolje slaže s tipovima, ali uključuje još više troška u izvršavanju i može se tvrditi da je samo malo manje ružna.
Iskreno, predložio bih da pokušate refaktorirati svoj kod tako da ne zahtijeva TypeScript za provođenje ovoga. Najlakše je zahtijevati da objekt ima ove vrijednosti kao ključeve (npr. samo napravite vrijednost tipa T ili možda Record<keyof T, any>) i koristite to umjesto (ili prije proizvodnje) niza. U svakom slučaju, nadam se da ovo pomaže. Sretno!
Poveznica na Playground za kod
Odgovor #2
Imam rješenje za osiguranje u vrijeme kompajliranja da niz sadrži sve vrijednosti nekog proizvoljnog tipa T. To je prilično blizu onome što želite - nedostaje provjera za sprječavanje duplikata - ali nekim ljudima možda neće trebati sprječavanje duplikata (npr. drugo pitanje označeno kao duplikat ovog pitanja nema taj zahtjev).
Tip NoRepeats iz odgovora @jcalz vjerojatno se može kombinirati s ovim ako želite nametnuti taj dodatni zahtjev.
Možemo osigurati da niz sadrži svaku vrijednost tipa T pisanjem generičke funkcije s dva tipa parametra. T je proizvoljan tip, a A je tip niza. Želimo provjeriti da su T[] i A identični. Lijep način da se to učini je pisanje uvjetnog tipa koji se rješava u never ako ovi dva tipa nisu podtipovi jedan drugoga:
type Equal<S, T> = [S, T] extends [T, S] ? S : never;
Želimo izričito odrediti parametar tipa T, kako bismo kompajleru rekli što da provjeri, ali želimo da se A zaključi iz stvarne vrijednosti, kako bi provjera imala smisla. Typescript ne dopušta odrediti neke, ali ne sve parametre tipa funkcije, no možemo to zaobići korištenjem curryiranja:
function allValuesCheck<T>(): <A extends T[]>(arr: Equal<A, T[]>) => T[] {
return arr => arr;
}
Zatim se ova funkcija može koristiti za traženje od kompajlera da provjeri sadrži li literal polja sve moguće vrijednosti proizvoljnog tipa:
type Foo = 'a' | 'b';
// ok
const allFoo = allValuesCheck<Foo>()(['a', 'b']);
// type error
const notAllFoo = allValuesCheck<Foo>()(['a']);
// type error
const someNotFoo = allValuesCheck<Foo>()(['a', 'b', 'c']);
Mane su u tome što tip greške nisu informativne; prva poruka o grešci jednostavno kaže Tip 'string' nije moguće dodijeliti tipu 'never', a druga poruka o grešci jednostavno kaže Tip 'string' nije moguće dodijeliti tipu 'Foo'. Dakle, iako kompajler informira o grešci, ne govori vam koji nedostaje ili je pogrešan.
Veza za igralište
