If you have a class with a literal member then you can use that property to discriminate between union members.
As an example consider the union of a Square and Rectangle, here we have a member kind that exists on both union members and is of a particular literal type:
If you use a type guard style check (==, ===, !=, !==) or switch on the discriminant property (here kind) TypeScript will realize that the object must be of the type that has that specific literal and do a type narrowing for you :)
functionarea(s:Shape) {if (s.kind ==="square") {// Now TypeScript *knows* that `s` must be a square ;)// So you can use its members safely :)returns.size *s.size; }else {// Wasn't a square? So TypeScript will figure out that it must be a Rectangle ;)// So you can use its members safely :)returns.width *s.height; }}
Exhaustive Checks
Quite commonly you want to make sure that all members of a union have some code(action) against them.
interfaceSquare { kind:"square"; size:number;}interfaceRectangle { kind:"rectangle"; width:number; height:number;}// Someone just added this new `Circle` Type// We would like to let TypeScript give an error at any place that *needs* to cater for thisinterfaceCircle { kind:"circle"; radius:number;}typeShape=Square|Rectangle|Circle;
As an example of where stuff goes bad:
functionarea(s:Shape) {if (s.kind ==="square") {returns.size *s.size; }elseif (s.kind ==="rectangle") {returns.width *s.height; }// Would it be great if you could get TypeScript to give you an error?}
You can do that by simply adding a fall through and making sure that the inferred type in that block is compatible with the never type. For example if you add the exhaustive check you get a nice error:
functionarea(s:Shape) {if (s.kind ==="square") {returns.size *s.size; }elseif (s.kind ==="rectangle") {returns.width *s.height; }else {// ERROR : `Circle` is not assignable to `never`const_exhaustiveCheck:never= s; }}
If using strictNullChecks and doing exhaustive checks, TypeScript might complain "not all code paths return a value". You can silence that by simply returning the _exhaustiveCheck variable (of type never). So:
A popular library that makes use of this is redux.
Here is the gist of redux with TypeScript type annotations added:
import { createStore } from'redux'typeAction= { type:'INCREMENT' }| { type:'DECREMENT' }/** * This is a reducer, a pure function with (state, action) => state signature. * It describes how an action transforms the state into the next state. * * The shape of the state is up to you: it can be a primitive, an array, an object, * or even an Immutable.js data structure. The only important part is that you should * not mutate the state object, but return a new object if the state changes. * * In this example, we use a `switch` statement and strings, but you can use a helper that * follows a different convention (such as function maps) if it makes sense for your * project. */functioncounter(state =0, action:Action) {switch (action.type) {case'INCREMENT':return state +1case'DECREMENT':return state -1default:return state }}// Create a Redux store holding the state of your app.// Its API is { subscribe, dispatch, getState }.let store =createStore(counter)// You can use subscribe() to update the UI in response to state changes.// Normally you'd use a view binding library (e.g. React Redux) rather than subscribe() directly.// However, it can also be handy to persist the current state in the localStorage.store.subscribe(() =>console.log(store.getState()))// The only way to mutate the internal state is to dispatch an action.// The actions can be serialized, logged or stored and later replayed.store.dispatch({ type:'INCREMENT' })// 1store.dispatch({ type:'INCREMENT' })// 2store.dispatch({ type:'DECREMENT' })// 1
Using it with TypeScript gives you safety against typo errors, increased refactor-ability and self documenting code.