"SING_AND_DANCE"were passed as an action, it would be silently ignored without error. However, in Typescript, because we specify the return type as
number, we will get a type error, as
undefinedis returned when
"SING_AND_DANCE"fails to match.
actionis initially typed as
"INCREMENT" | "DECREMENT" | "SING_AND_DANCE". With every
casethat is handled, the handled literal value is ejected from the type; as such, the inferred type of
"DECREMENT" | "SING_AND_DANCE"after the first case, and then just
"SING_AND_DANCE"after the second (a process known as type narrowing). Because the function exits at this point, the type checker knows that there is an unhandled type remaining, and emits an error.
CounterActions to all be records that correspond to a shared interface:
CounterSingAndDanceActionboth extend a shared generic interface. When there are combined into
CounterAction, they end up forming a discriminated union or algebraic data type (ADT). A discriminated union is composed of a discriminant, which is a propery whose value is unique across types, and a union, which is a sum type of all the discriminated types. Here,
type: "COUNTER_UPDATE" | "COUNTER_SING_AND_DANCE"is the discriminant, while
type CounterActionis the union. (Note that we never explicitly specified that
typeshould be the discriminant -- it is automatically inferred. It's possible to have multiple discriminants, as long as you adhere to the rules of discriminated unions.)
DECREMENTactions into a single update action that takes a number to add (ignoring the fact that this is now no longer really a "counter").
typefield. With this, we've added the ability to pass largely arbitrary values, as long as they are tagged with the a discriminant.
defaulthandler to the switch statement. In the process of narrowing, if all permissible types are narrowed out, the resultant type is
never, a special type signifying a value that can never occur. As such, our
defaulthandler must emit a value of type
neverfor our function to type-check.
never. Thus, we can simply throw an error here, to make us aware something went wrong at runtime. While a simple
throwis sufficient, I describe a utility here that emits a slightly more informative error message for error tracking purposes.
void, the unhandled case would not be reported as a type error. Here, because we are passing a possibly non-
matchNotExhaustivewhich expects a
never, we get a type error independent of the reducer's return type.