TypeScript Basics: Type Unions
Welcome to the Type Unions lesson!
This lesson is shown as static text below. However, it's designed to be used interactively. Click the button below to start!
TypeScript was carefully designed as an extension of JavaScript. It needs to be able to add static types to existing JavaScript code.
Many JavaScript functions take arguments that can be one of multiple types. Here's one such function. The
Type1 | Type2syntax means "either a Type1 or a Type2". (Remember that you can typetype errorif the code will fail to compile due to a type error.)>
function isNumber(arg: string | number): boolean {return typeof arg === 'number';}- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
isNumber(1);Result:
true
- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
isNumber('cat');Result:
false
- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
isNumber(undefined);Result:
type error: Argument of type 'undefined' is not assignable to parameter of type 'string | number'.
An "or" type,
Type1 | Type2, is called a union type.You may have encountered JavaScript arrays'
concatmethod before. It concatenates (combines) two arrays. Let's recreate it to see union types in action. To keep the example focused, we'll only support arrays of numbers.This example will use JavaScript's (and TypeScript's) "spread" syntax:
[...a1, ...a2]. That syntax means "build an array that contains all of the elements froma1followed by all of the elements froma2".>
function concat(a1: number[], a2: number[]) {return [...a1, ...a2];}concat([1], [2]);Result:
[1, 2]
This works, but is missing a feature of the real
concatmethod in JavaScript. In the real method, we can do[1].concat(2)to get[1, 2].We can use a union type to add this feature to our own
concat.>
function concat(a1: number[], a2: number | number[]) {/* Wrap a2 in an array if it's not one already. */let a2Array = Array.isArray(a2) ? a2 : [a2];return [...a1, ...a2Array];}- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
concat([1], [2]);Result:
[1, 2]
- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
concat([1], 2);Result:
[1, 2]
- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
concat([1], '2');Result:
type error: Argument of type 'string' is not assignable to parameter of type 'number | number[]'.
As usual, we can combine any types with a union. The example below is like our
concatexample: it accepts an array or a single user. But this time, we extract the users' names and return them as an array.>
type User = {name: string};function userNames(u: User[] | User): string[] {return Array.isArray(u)? u.map(user => user.name): [u.name];}- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
userNames({name: 'Amir'});Result:
['Amir']
- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
userNames([{name: 'Amir'}, {name: 'Betty'}]);Result:
['Amir', 'Betty']
- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
userNames({email: 'betty@example.com'});Result:
type error: Object literal may only specify known properties, and 'email' does not exist in type 'User | User[]'.
We can use union types in any situation where we can use other types. They can have names, for example.
>
type ZipCode = string | number;- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
let seattle: ZipCode = 98101;seattle;Result:
98101
- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
let cleveland: ZipCode = '44106';cleveland;Result:
'44106'
- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
let nowhere: ZipCode = null;nowhere;Result:
type error: Type 'null' is not assignable to type 'ZipCode'.
TypeScript won't allow us to mix union types. For example, a
string | numbercan't be assigned to a plainnumber. We can reason about this by analogy to the physical world.Suppose that a drawer is labeled "forks". We have a handful of forks and knives. If we put the forks and knives in the drawer, we've violated the label. Likewise, when we put a
Fork | Knifeinto aForkvariable, we've violated the type. It would allow some situations where theForkvariable ended up holding aKnife.>
function getClevelandZipCode(): string | number {return 44106;}let zip: string | number = getClevelandZipCode();zip;Result:
44106
>
function getClevelandZipCode(): string | number {return 44106;}let zip: number = getClevelandZipCode();zip;Result:
type error: Type 'string | number' is not assignable to type 'number'. Type 'string' is not assignable to type 'number'.
That last example might seem a bit strange. We can see that
getClevelandZipCodereturns44106, which is a number. But we explicitly told the compiler that the function returns astring | number. The compiler doesn't try to find a "better" type than the one that we provided. It only checks against the provided type,string | number.What happens if we assign a
stringto astring | number? We can think about this by analogy to the physical world again. If a drawer is labeled "forks and knives", and we put a knife in, then we've satisfied the label. Likewise, when we put aForkinto aFork | Knifevariable, we've satisfied the types.>
function cleveland(): number { return 44106; }const cleveland2: string | number = cleveland();cleveland2;Result:
44106