Hur man avgränsar TypeScript generisk typ med nästlad gränssnitt
Jag försöker skapa ett generiskt gränssnitt för en funktion som tar emot tre argument. När jag fyller i det första argumentet bör alternativen för det andra vara inom omfattningen av det första. Det tredje argumentet bör sedan vara inom omfattningen av det andra argumentet.
Min nuvarande kod ser ut så här:
type SettingActionCreator<T> = <
L extends T,
K extends keyof L,
C extends keyof L[K]
>(
type: K,
settingKey: keyof L[K],
settingValue: L[K][C]
)
export interface SomeSettings {
[Type.TypeA]: {
settingA: "value1" | "value2";
settingB: "value3 | "value4";
};
[Type.TypeB]: {
...
};
[Type.TypeC]: {
...
};
}
const setSomeSetting: SettingActionCreator<SomeSettings> = (
type,
settingKey,
settingValue
) =>
// do something
Så när jag vill kalla setSomeSetting med argument förväntar jag mig att mitt scope blir mindre för varje argument jag skickar in i det.
Om jag till exempel fyller i det första argumentet, ser SettingActionCreator till att det andra argumentet (settingKey) bara kan vara settingA eller settingB. Hittills fungerar allt som förväntat.
setSomeSetting(Type.TypeA, ....) // <-- settingKey accepts "settingA" and "settingB"
Men för det tredje argumentet verkar jag inte kunna lösa det. När jag sätter det första argumentet och det andra argumentet skulle jag förvänta mig att det tredje argumentet endast accepterar föreningsalternativen inom det valda settingKey-argumentet. Men istället för det får jag alla alternativ inom Type.TypeA. Så både settingValues för settingA och settingB
Med den här koden:
setSomeSetting(Type.TypeA, "settingA", "value3") // <-- settingValue should only accept "value1" or "value2"
Jag skulle förvänta mig ett fel som:
TS2345: Argument of type '"value3"' is not assignable to parameter of type `"value1" | "value2"
Men istället får jag inga typfel eftersom det accepterar både värdena för settingA och settingB.
Har någon ett förslag på vad jag gör fel?
Svar #1
Du behöver bevisa för TypeScript att settingKey är relaterad till settingValue - settingKey: keyof L[K] i din deklaration förvirrar det (jag är inte säker på varför - det känns som en bugg att type C extends keyof L[K] != keyof L[K], men det är definitivt en begränsning i typinferenskoden ändå).
Ändra typen på det argumentet till settingKey: C och TypeScript kommer att kunna förstå relationen mellan de tre argumenten:
type SettingActionCreator<T> = <
L extends T,
K extends keyof L,
C extends keyof L[K]
>(
type: K,
settingKey: C, // <-- changed from keyof L[K]
settingValue: L[K][C]
) => void;
declare const Type: {
readonly TypeA: unique symbol,
readonly TypeB: unique symbol,
readonly TypeC: unique symbol
}
export interface SomeSettings {
[Type.TypeA]: {
settingA1: "valueA1" | "valueA2";
settingA2: "valueA3" | "valueA4";
},
[Type.TypeB]: {
settingB1: "valueB1" | "valueB2",
settingB2: "valueB3" | "valueB4"
},
[Type.TypeC]: {
//
}
}
const setSomeSetting: SettingActionCreator<SomeSettings> = (
type,
settingKey,
settingValue
) => {}
Detta resulterar i den typ av fel som du letar efter:
setSomeSetting(Type.TypeB, "settingB1", "valueA3")
// Argument of type '"valueA3"' is not assignable to parameter of type '"valueB1" | "valueB2"'.
