typescript - Hur man kastar ett typfel när ytterligare nycklar av en viss typ läggs till i ett objekt
Givet att jag har en typ MyType
type MyType = {
foo: number;
bar: string;
baz: string;
}
Jag skulle vilja definiera en array requiredFields som innehåller alla nycklar av MyType vars värde är en specifik typ T. Så att om en ytterligare nyckel av typen T senare läggs till i MyType, bör requiredFields ge ett typfel eftersom den nya nyckeln nu saknas.
Det kan se ut ungefär så här:
const numberFields: ExtractKeysByType<MyType, number> = ['foo'] // ✅
const stringFields: ExtractKeysByType<MyType, string> = ['bar'] // ❌ error - missing "baz"
Svar #1
Genom att kombinera två andra svar fick jag det resultat jag letade efter. Men det sistnämnda svaret har en stor GÖR INTE DET HÄR-varning framför sig, så ta detta med samma försiktighet:
Steg 1: Hur man extraherar nycklar av viss typ från ett objekt
type ExtractKeyUnionByType<Obj, Type> = {
[Key in keyof Obj]: Obj[Key] extends Type ? Key : never
}[keyof Obj]
Detta är nära det jag letade efter, men returnerar en unionstyp istället för en tupltyp.
ExtractKeyUnionByType<MyType, string> // => 'bar' | 'baz'
Och om du vill inkludera valfria typer, lägg till undefined i unionen
type MyType = {
foo?: number;
bar: string;
baz?: string;
obj: MyOtherType
}
ExtractKeyUnionByType<MyType, string | undefined> // => 'bar' | 'baz' | undefined
Steg 2: Hur man omvandlar en unionstyp till en tuplet-typ (påminnelse, författaren vill verkligen inte att du använder denna kod)
// oh boy don't do this type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never type LastOf<T> = UnionToIntersection<T extends any ? () => T : never> extends () => infer R ? R : never // TS4.0+ type Push<T extends any[], V> = [...T, V] // TS4.1+ type TuplifyUnion<T, L = LastOf<T>, N = [T] extends [never] ? true : false> = true extends N ? [] : Push<TuplifyUnion<Exclude<T, L>>, L>
type BarAndBazTuple = TuplifyUnion<
NonNullable<ExtractKeyUnionByType<MyType, string | undefined>>
> // => ['bar', 'baz']
Och du kan nu starkt typa en tuple, så att det kommer att ge ett fel om du lägger till några extra egenskaper eller tar bort några egenskaper.
const requiredFields: BarAndBazTuple = ['bar', 'baz'] // ✅
const missing: BarAndBazTuple = ['bar'] // ❌ error
const extra: BarAndBazTuple = ['bar', 'baz', 'other'] // ❌ error
Svar #2
Målet med att utlösa ett typfel när en nyckel av MyType av typen T saknas från en array kan uppnås enklare genom att först definiera ett objekt intermediateObj med de aktuella nycklarna, och sedan använda Object.keys() för att omvandla det objektet till en array requiredFields.
Genom att använda en unionstyp och Record kan ett fel uppstå när en nyckel saknas; till skillnad från en Array<UnionType> som bara skulle ge fel om en felaktig egenskap lades till (inte om en saknas).
Först, två hjälptyper. Den första är lånad från detta underbara andra svar. Den andra modifierar den första för att även inkludera valfria egenskaper.
type ExtractRequiredKeysByType<Obj, Type> = {
[Key in keyof Obj]: Obj[Key] extends Type ? Key : never
}[keyof Obj]
type ExtractKeysByType<Obj, Type> = NonNullable<
ExtractRequiredKeysByType<Obj, Type | undefined>
>
ExtractKeysByType kan sedan användas för att definiera ett objekt som måste innehålla alla string-nycklar från MyType
type StringKeys = ExtractKeysByType<MyType, string>
const intermediateObj: Record<StringKeys, true> = {
bar: true,
baz: true,
}
Och slutligen kan det mellanliggande objektet omvandlas till den önskade arrayen via Object.keys()
const requiredFields = Object.keys(offerTermDateFieldsObj) as StringKeys[]
Nu, om någon kommer och lägger till ett annat strängvärde anotherString till MyType, kommer ett fel att kastas i definitionen av intermediateObj
Egenskap
'anotherString'saknas i typen{ bar: true; baz: true }men krävs i typenRecord<StringKeys, true>.
