Hur man skiljer på strukturellt identiska typer i TypeScript?
Jag använder ett bibliotek som har flera gränssnitt som är strukturellt ekvivalenta. Som detta:
interface DataType { ... some stuff here ... }
interface DataTypeBoolean extends DataType {}
interface DataTypeJSON extends DataType {}
... around 20 more, not all of them equivalent
Och det ger mig ett objekt vars typ är:
def: {
flag: {
type: DataTypeBoolean;
};
data: {
type: DataTypeJSON;
};
}
Jag vill generera en typ som översätter dessa till primitiva typer:
export type Types<T> = { [K in keyof T]:
T[K] extends { type: DataTypeBoolean } ? boolean :
T[K] extends { type: DataTypeJSON } ? object :
... some more clauses
undefined;
}
Men eftersom DataTypeBoolean och DataTypeJson är ekvivalenta, ger DataTypeJSON true för extends DataTypeBoolean och därmed ger Types<def> mig:
def: {
flag: boolean;
data: boolean;
}
För att det där Types<def> ska fungera, skulle jag behöva hitta ett sätt att skapa DataTypeBooleanStrict och DataTypeJsonStrict så att DataTypeBooleanStrict extends DataTypeBoolean och DataTypeJSONStrict extends DataTypeJSON (så att jag fortfarande kan respektera bibliotekets gränssnitt), men också att DataTypeBooleanStrict inte utökar DataTypeJSONStrict och vice versa.
Hur kan jag uppnå detta?
Det skulle vara mycket trevligt att uppnå det med en generisk typ som Strict<DataTypeBoolean> istället för att behöva definiera alla 30 av dessa manuellt, men manuellt är också bättre än ingenting.
Svar #1
Som det har noterats är typesystemet i TypeScript strukturellt och därför är två typer med samma form faktiskt samma typ, även om de kan ha olika namn. Sättet att hantera detta är generellt att modifiera dina typer så att de skiljer sig både strukturellt och nominellt, som nämnts i TypeScripts FAQ-punkt: "Hur förhindrar jag att två typer är strukturellt kompatibla?"
Nu i det här fallet verkar de problematiska gränssnitten finnas i ett bibliotek som du inte kan eller inte vill ändra. Lyckligtvis kan du använda deklarationssammanfogning för att öppna gränssnittsdefinitioner på nytt och lägga till nya egenskaper till dem. Troligtvis vill du lägga till något valfritt så att det är okej att inga faktiska sådana egenskaper finns vid körning. Till exempel:
///// DECLARATION MERGING //////
interface DataTypeBoolean {
__type?: "boolean"
}
interface DataTypeJSON {
__type?: "JSON"
}
///////////////////////////////
Tänk på att om du importerar dina gränssnitt från en modul, kan du behöva använda declare module runt dina sammanslagna definitioner, som visas i handboken som
declare module "./myLib" {
interface DataTypeBoolean {
__type?: "boolean"
}
interface DataTypeJSON {
__type?: "JSON"
}
}
Hur som helst, när vi väl gör det bör saker börja fungera för dig, eftersom kompilatorn kan se att de typerna är olika:
type Hmm = Types<Def>;
/* type Hmm = {
flag: boolean;
data: object;
} */
Okej, hoppas att det hjälper. Lycka till!
Länk till kod
