Definiera en TypeScript-array som består av en av specifika strängar
Jag skulle vilja definiera en typ där en Array kan inkludera en av en lista med specifika strängar, till exempel:
const foo = "foo";
const bar = "bar';
const baz = "baz':
// acceptable
[foo, bar]
// acceptable
[foo, bar, baz]
// unacceptable
[foo, foo]
// unacceptable
[foo, bar, bar]
Hur gör jag det?
Svar #1
Jag älskar verkligen den här typen av frågor, även när svaret är i formen "du kan göra bakåtvolter och komma nära en lösning men det kanske inte är värt det". Vilket detta svar är. Låt oss göra några bakåtvolter!
Först, försök att representera en begränsning NoRepeats<T> där om T har alla distinkta egenskapstyper, kommer resultatet att vara T, men om till och med två nycklar har samma egenskapstyp, kommer resultatet att vara never:
type NoRepeats<T> = true extends (
(keyof T) extends (infer K) ? K extends any ? (
T[K] extends T[Exclude<keyof T, K>] ? true : never
) : never : never
) ? "No Repeats Please" : T
Den använder distributiva villkorstyper för att dela upp keyof T i individuella nycklar K, och jämför sedan T[K] med T[Exclude<keyof T, K>] där Exclude<keyof T, K> betyder "alla andra nycklar förutom K". Om de matchar, blir hela grejen "No Repeats Please" (en slags enkel felmeddelande istället för ogiltiga typer). Om ingen av dem matchar, är svaret T.
Låt oss se hur det beter sig:
interface X {a: string, b: number, c: boolean}; // no repeats
declare const x: NoRepeats<X>; // x is of type X
interface Y {a: string, b: number, c: string}; // a and c are string
declare const y: NoRepeats<Y>; // y is of type "No Repeats Please"
Nu, om du skickar in array-typer, vill du inte inspektera varje möjlig egenskap för dubbletter, eftersom arrayer har metoder som forEach och map och egenskaper som length. Och dessutom försöker du egentligen bara kontrollera tuples, inte allmänna arrayer. Det beror på att allt kompilatorn vet om en array är unionen av alla dess elementtyper, och den har inget sätt att avgöra om det finns dubbletter. Så du inspekterar bara de sifferliknande nycklarna i en tuple ("0", "1", "2", upp till men inte inklusive tuple-längden).
Så vi behöver ta bort alla nycklar som är gemensamma för alla arraytyper... vilket jag kommer att kalla "att ta bort" tuplen:
type StripTuple<T extends any[]> = Pick<T, Exclude<keyof T, keyof any[]>>
Och se till att vi bara kontrollerar tupler och inte generiska arrayer:
type NoRepeatTuple<T extends any[]> =
number extends T['length'] ? "Must Be Tuple" : NoRepeats<StripTuple<T>>
Så, låt oss prova detta på tupler och arrayer:
declare const z: NoRepeatTuple<[string, number, boolean]> // {0: string, 1: number, 2:boolean}
declare const a: NoRepeatTuple<[string, number, string]> // "No Repeats Please"
declare const b: NoRepeatTuple<string[]> // "Must Be Tuple"
Så vi börjar komma närmare. Nu finns det inget sätt att deklarera att en typ T är en NoRepeatTuple<T> eftersom det är ett cirkulärt villkor. Men du kan göra en liknande trick som man kan använda för att approximera exakta typer, genom att skapa en funktion som endast accepterar tupler utan upprepningar:
function requireNoRepeats<V extends "foo" | "bar" | "baz", T extends Array<V>>(
t: T & NoRepeatTuple<T>
) { }
Parametern t som anges som T & NoRepeatTuple<T> kommer att få kompilatorn att härleda typen av t som skickas in som T, och sedan kontrollera den med NoRepeatTuple<T>. Låt oss se om vi kan prova det:
const foo = "foo";
const bar = "bar";
const baz = "baz";
requireNoRepeats([foo, bar]) // error, must be tuple
Hoppsan! Åh, problemet är att värdet [foo, bar] härleds till att vara en array och inte en tuple. Det är ett problem i TypeScript och jag tror inte att det kommer att fixas omedelbart.
Så vi måste övertyga kompilatorn att tolka något som en tuple. Ett sätt jag har använt tidigare är att skriva en funktion som härleder tuple-typer. När TS3.0 snart släpps kan du skriva det kortfattat med tuple rest/spread som
export type Lit = string | number | boolean | undefined | null | void | {};
export const tuple = <T extends Lit[]>(...args: T) => args;
Tills dess kan du använda en mer utförlig funktion.
Okej, äntligen, låt oss prova det:
requireNoRepeats(tuple(foo, bar)) // okay
requireNoRepeats(tuple(foo, bar, baz)) // okay
requireNoRepeats(tuple(foo, bar, bar)) // error, no repeats please
requireNoRepeats(tuple(foo, foo)) // error, no repeats please
Så, det fungerar alltihop. Hurra! Hurra? Som ni kan se finns det många förbehåll och typomvandlingar där. Inte något jag skulle känna mig särskilt bekväm med att använda i någon annans kod, åtminstone inte utan mycket testning.
Hur som helst, hoppas det ger dig några idéer. Lycka till!
