I have two unioned types, one has a property and the other one hasn't. I assumed that checking for the existence of that property would allow me to narrow it down, but it isn't working.
I've created this Playground repro. This other very similar thing seems to work just fine. Am I using unions the wrong way?
Here's the code for the sake of completeness:
export interface AuthenticatedProfile {
readonly userId: string;
readonly name: string;
}
export interface AnonymousProfile {
readonly userId: undefined;
readonly otherProp: string;
}
export type Profile = AnonymousProfile | AuthenticatedProfile;
function handleProfile(prof: Profile) {
if (prof.userId) {
console.log(prof.name);
}
}
Thanks!
A common technique for working with unions is to have a single field which uses literal types which you can use to let TypeScript narrow down the possible current type. For example, we’re going to create a union of three types which have a single shared field. ts typeNetworkLoadingState= {
Property 'code' does not exist on type 'NetworkLoadingState'. // By switching on state, TypeScript can narrow the union // down in code flow analysis switch(state.state) {
TypeScript follows possible paths of execution that our programs can take to analyze the most specific possible type of a value at a given position. It looks at these special checks (called type guards) and assignments, and the process of refining types to more specific types than declared is called narrowing .
JavaScript has an operator for determining if an object has a property with a name: the in operator. TypeScript takes this into account as a way to narrow down potential types. For example, with the code: "value" in x. where "value" is a string literal and x is a union type.
You can use type guards to restrict the type of the prof parameter.
export interface AuthenticatedProfile {
readonly userId: string;
readonly name: string;
}
export interface AnonymousProfile {
readonly userId: undefined;
readonly otherProp: string;
}
export type Profile = AnonymousProfile | AuthenticatedProfile;
function isAuthenticatedProfile(prof: Profile): prof is AuthenticatedProfile {
return (<AuthenticatedProfile>prof).name !== undefined;
}
function isAnonymousProfile(prof: Profile): prof is AnonymousProfile {
return (<AnonymousProfile>prof).otherProp !== undefined;
}
function handleProfile(prof: Profile) {
if (isAuthenticatedProfile(prof)) {
console.log(prof.name);
} else if (isAnonymousProfile(prof)) {
console.log(prof.otherProp);
}
}
You can read more about type guards in typescript in the Advanced Types section in the handbook.
You can do the following:
export interface AuthenticatedProfile {
readonly type: "AuthenticatedProfile";
readonly userId: string;
readonly name: string;
}
export interface AnonymousProfile {
readonly type: "AnonymousProfile";
readonly userId: undefined;
readonly otherProp: string;
}
export type Profile = AnonymousProfile | AuthenticatedProfile;
function handleProfile(prof: Profile) {
if (prof.type === "AnonymousProfile") {
console.log(prof.name); // Error
console.log(prof.otherProp); // OK
console.log(prof.userId); // OK
}
if (prof.type === "AuthenticatedProfile") {
console.log(prof.name); // OK
console.log(prof.otherProp); // Error
console.log(prof.userId); // OK
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With