TypeScript uppslags-/villkorstyper och unioner
Jag har en enkel fråga: Är det möjligt att få en typ av en del av en union i TypeScript?
Till exempel kan du ofta använda uppslagstyper på det här sättet:
interface Person {
name: string;
}
type Name = Person['name']
Nu antar jag att det inte är möjligt med fackföreningar som detta:
type Entity =
{ __type: 'Company', name: string }
| { __type: 'Employee', firstName: string };
Så, finns det något sätt att få en del av ett fackförbund? Något sådant här:
type Company = DoTheMagic<Entity, { __type: 'Employee' }>
const company: Company = ...;
console.log(company.name) // OK
console.log(company.firstName) // Compile error
Svar #1
Vi kan använda den konditionella typen Extract<T, U>. Om T är en union kommer resultatet av Extract att vara alla medlemmar av T-unionen som uppfyller begränsningen U (även känd som T extends U).
type Company = Extract<Entity, { __type: 'Employee' }>
// Same as
type Company = {
__type: "Employee";
firstName: string;
}
Svar #2
Det korrekta svaret publicerades av Titian Cernicova-Dragomir. Jag bestämde mig dock för att lägga upp en komplett lösning på mitt bredare problem.
Vi arbetar med en ApolloClient och vi får ofta unioner. På grund av stark typning behöver du många fula villkor för att komma åt rätt egenskaper och hålla TypeScript nöjd.
Så jag kom på följande asTypeGuard-funktion:
function asTypeGuard<Typename extends string>(typename: Typename) {
return function<Entity extends { __typename: string }>(
entity: Entity
): entity is Extract<Entity, { __typename: typeof typename }> {
return entity.__typename === typename;
};
}
const isCompany = asTypeGuard('Company');
const isEmployee = asTypeGuard('Employee');
let entity: { __typename: 'Company'; name: string } | { __typename: 'Employee'; firstName: string };
if (isCompany(entity)) {
console.log(entity.name);
console.log(entity.firstName) // Property 'firstName' does not exist on type '{ __typename: "Company"; name: string; }
} else if (isEmployee(entity)) {
console.log(entity.name); // Property 'name' does not exist on type '{ __typename: "Employee"; firstName: string; }
console.log(entity.firstName)
}
