TypeScript’s discriminated union types (aka tagged union types) allow you to model a finite set of alternative object shapes in the type system. The compiler helps you introduce fewer bugs by only exposing properties that are known to be safe to access at a given location. This lesson shows you how to define a generic Result<T>
type with a success case and a failure case. It also illustrates how you could use discriminated unions to model various payment methods.
Hi Marius, this is a really great course!
I have an issue with discriminated union types.
I want to discriminate the props of my React component by visibleListType
property. However if I am now passing the prop visibleListType={"ListView'}
and e.g. keyExtractor={item => item.userId}
to the AlwaysVisibleList
component, I'd expect the TS complier to warn me about using the keyExtractor
prop. This is because the keyExtractor
is part of the FlatListProperties, but not the ListViewProperties.
Do you know what I am doing wrong here? I have strictNullChecks
enabled.
interface OwnProps {
customInsetWhenKeyboardIsHidden?: number;
customInsetWhenKeyboardIsShown?: number,
}
type VisibleListType = 'ListView' | 'FlatList' | 'SectionList' ;
interface SectionListProps extends SectionListProperties<any>, OwnProps {
visibleListType: VisibleListType;
}
interface FlatListProps extends FlatListProperties<any>, OwnProps {
visibleListType: VisibleListType;
}
interface ListViewProps extends ListViewProperties, OwnProps {
visibleListType: VisibleListType;
}
type Props =
| SectionListProps
| FlatListProps
| ListViewProps
interface State {
}
class AlwaysVisibleList extends React.Component <Props, State> {
\*...*\
}
@Thorben: Some types are missing in your code example (e.g. SectionListProperties<T>
), but from what I can see, you're not specifying a different discriminant for each of your props interfaces — you're defining a union type once ('ListView' | 'FlatList' | 'SectionList'
), which is then shared by all props interfaces.
This is not how discriminants (aka tags) are meant to be used. Every props interface should define a property of the same name and a unique literal type, which is then used to differentiate between the possible cases. That doesn't work if you're using the same union type for every case.
Hello Marius,
Thank you for your course.
I've found very tricky part.
when you write in the switch expression method.kind
(like in your example) it works perfect.
but when I use destructurisation
const { kind } = method;
and put kind
to the expression, the compiler complains to method.email
: Property 'email' does not exist on type 'PaymentMethod'. Property 'email' does not exist on type 'Cash'.
Could you explain whats wrong?
Thank you
@Actum: The TypeScript compiler only narrows the type of the method
parameter if you're checking method.kind
directly. It does not track that you've stored the value of method.kind
within the kind
local variable. You'll have to stick to method.kind
to have the compiler narrow the type.
Argument of type '{ kind: string; email: string; }' is not assignable to parameter of type 'PaymentMethod'.
Type '{ kind: string; email: string; }' is not assignable to type 'CreditCard'.
Property 'cardNumber' is missing in type '{ kind: string; email: string; }'.
const myPayment: {
kind: string;
email: string;
}
ts complained above, Could you explain whats wrong? Thanks
@yu: Could you post a small code example that produces the error you're talking about?
Is there amy way to have a class implement a discriminated union? It seems to throw an error and i’ve read elsewhere of people having this issue. Would be a shame to lose this type specificity in a class where it is readily available in a POJO hash/dictionary.
@Ken: No, that's not possible as far as I am aware. You'll have to stick with POJOs if you want to use discriminated unions.