typescript - Jak vyvolat chybu typu, když jsou do objektu přidány další klíče konkrétního typu
Vzhledem k tomu, že mám typ MyType
type MyType = {
foo: number;
bar: string;
baz: string;
}
Chtěl bych definovat pole requiredFields, které obsahuje všechny klíče MyType, jejichž hodnota je určeného typu T. Takže pokud bude později přidán další klíč typu T do MyType, requiredFields by měl vyvolat chybu typu, protože nový klíč nyní chybí.
To by mohlo vypadat například takto:
const numberFields: ExtractKeysByType<MyType, number> = ['foo'] // ✅
const stringFields: ExtractKeysByType<MyType, string> = ['bar'] // ❌ error - missing "baz"
Odpověď č. 1
Kombinací dvou dalších odpovědí jsem dostal výsledek, který hledám. Ale u té druhé odpovědi je velké NEDĚLEJ TO varování, takže to berte se stejnými opatrnostmi:
Krok 1: Jak získat klíče určitého typu z objektu
type ExtractKeyUnionByType<Obj, Type> = {
[Key in keyof Obj]: Obj[Key] extends Type ? Key : never
}[keyof Obj]
Tohle je blízko tomu, co jsem hledal, ale vrací to unijní typ místo n-tice.
ExtractKeyUnionByType<MyType, string> // => 'bar' | 'baz'
A pokud chcete zahrnout volitelné typy, přidejte undefined do unie
type MyType = {
foo?: number;
bar: string;
baz?: string;
obj: MyOtherType
}
ExtractKeyUnionByType<MyType, string | undefined> // => 'bar' | 'baz' | undefined
Krok 2: Jak převést typ unie na typ n-tice (připomenutí, autor opravdu nechce, abyste tento kód použili)
// 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']
A nyní můžete silně typovat tuple, takže se zobrazí chyba, pokud přidáte jakékoliv další vlastnosti nebo odstraníte nějaké vlastnosti.
const requiredFields: BarAndBazTuple = ['bar', 'baz'] // ✅
const missing: BarAndBazTuple = ['bar'] // ❌ error
const extra: BarAndBazTuple = ['bar', 'baz', 'other'] // ❌ error
Odpověď č. 2
Cíle vyvolání typu chyby, když klíč MyType typu T chybí v poli, lze dosáhnout snadněji nejprve definováním objektu intermediateObj s příslušnými klíči a poté použitím Object.keys() k přeměně tohoto objektu na pole requiredFields.
Použitím unijního typu a Record může být vyvolána chyba, pokud klíč chybí; na rozdíl od Array<UnionType>, který by chybu vyvolal pouze v případě, že byla přidána nesprávná vlastnost (nikoli pokud některá chybí).
Nejprve dva užitečné typy. První je převzat z tohoto skvělého jiného odpovědi. Druhý upravuje první tak, aby také zahrnoval volitelné vlastnosti.
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 lze poté použít k definování objektu, který musí obsahovat všechny string klíče z MyType
type StringKeys = ExtractKeysByType<MyType, string>
const intermediateObj: Record<StringKeys, true> = {
bar: true,
baz: true,
}
A nakonec může být tento mezilehlý objekt převeden na požadované pole pomocí Object.keys()
const requiredFields = Object.keys(offerTermDateFieldsObj) as StringKeys[]
Teď, pokud někdo přijde a přidá další hodnotu řetězce anotherString do MyType, dojde k chybě při definici intermediateObj
Vlastnost
'anotherString'chybí v typu{ bar: true; baz: true }, ale je vyžadována v typuRecord<StringKeys, true>.
