In TypeScript, union types are a part of the static type system that enhances code flexibility. With union types, you can declare two or more possible types for a variable, specifying the range of type values that these variables can hold. It is one of the key features of TypeScript because it enables the handling of different types of values under the same variable. Let's explore how union types work in TypeScript.
Basic syntax and type declarations
When defining a union type in TypeScript, the colon : is used for declaration, and the pipe | character is used to separate and combine the possible types into a union type. This means that a variable declared as a union type can hold a value of any of the individual types we specify.
Let's take a look at an example:
let myVariable: number | string;
In the code above, what we're telling TypeScript is that myVariable can be either a string or a number, but not both of them at the same time. Additionally, each of the alternative types we separated with the pipe character is called a constituent. Constituents are the individual types that make up a union type. So, in our example provided above, our constituents are string and number. You can combine any number, order, and type of constituents according to your preference or the task at hand.
We can also use literal types with union types in TypeScript. This allows us to define a variable that can hold one of several specific values, going beyond just regular types. However, in this case, we need to use a new keyword type instead of let or const.
type ButtonType = "submit" | "reset" | "button";
let myButton: ButtonType;
In this example, we declared a custom type ButtonType, which is a union of literal types. It means that a variable of type ButtonType can only have one of three specific string values: "submit", "reset", or "button". Then, in the second line, we declared a variable myButton with this type, to make that myButton can only store one of these three values.
type keyword will be covered in another topic on type aliases. That is why we will not go into detail here and, for now, our focus is on showing how union types work with literal types. Specific uses of union types
Union types can also be used with function parameters and return values. This way, functions can accept and return different types of parameters and values.
For example, in the following code, the formatInput function can accept either a string or a number as an argument and will always return a string:
function formatInput(input: string | number): string {
return String(input);
}
In the code snippet below, the function formatInput both takes in and returns a union:
function formatInput(input: string | number): string | number {
return input;
}
So, the function formatInput is set up to accept input that's either a string or a number, and it returns the input unchanged. The type of the result can be a string or a number, matching the type of the input.
Additionally, union types can be used with arrays and tuples. For instance, you can create an array that can contain either product names (as strings) or product IDs (as numbers), like this:
let products: (string | number)[];
Or you can create a tuple that can hold a product ID and its name or availability status:
let productInfo: [number, string | boolean];
In this case, the tuple productInfo contains a pair of information elements about a product. The first element is a number, representing the product ID. The second element can be either a string or a boolean. If it's a string, it represents the product's name. If it's a boolean, it indicates the product's availability, where true means "available" and false means "unavailable".
Understanding union type errors
Union type errors typically occur when you try to assign a value of a type that isn't included in the union. It would be like trying to fit a square peg into a round hole: trying to fit a value that doesn't align with any of the expected types. And TypeScript will notify you that it doesn't match the allowed types!
For example, when we try to assign a number or a string to myVariable declared in the previous section, TypeScript will accept it because these types are allowed in our declared union type. However, if you try to assign a value of a type not included in the union, such as a boolean value, TypeScript will throw an error.
myVariable = 10; // OK
myVariable = "abc"; // OK
myVariable = true; // Error: Type 'boolean' is not assignable to type 'number | string'
The same principle also applies to literal types. In the code below, the variable myButton was also defined in the previous section as a specific type ButtonType. This type only accepts the values "submit", "reset", or "button", and assigning it any other value, such as "other", will result in a type error.
myButton = "submit"; // OK
myButton = "reset"; // OK
myButton = "button"; // OK
myButton = "other"; // Error: Type '"other"' is not assignable to type 'ButtonType'.Type verification with unions
Now let's go back to myVariable union type variable declaration. When you have a variable like myVariable that can be either a number or a string, you might run into problems when you try to use methods that only work on one type. For example, you might want to convert all characters to lowercase when myVariable is a string, using .toLowerCase() like this:
myVariable = myVariable.toLowerCase(); // Property 'toLowerCase' does not exist on type 'number | string'.
Here, TypeScript will give you an error, because .toLowerCase() only works for strings. This is because TypeScript does not know whether this method will be appropriate to the value of the variable at runtime. To avoid this error, you can do a type check to make sure that myVariable is actually a string before applying the .toLowerCase method, as follows:
if (typeof myVariable === 'string') {
myVariable = myVariable.toLowerCase();
}
When you use the typeof check, TypeScript will confirm that myVariable is indeed a string and understands that now it's safe to use the toLowerCase() method. This additional check layer will prevent errors and ensure the code functions correctly when dealing with different types within a union type.
Conclusion
Union types in TypeScript allow variables to hold different types of values, improving code flexibility. A union can contain multiple kinds and even specific values. However, TypeScript will alert you if you try to assign a value that is not one of the defined types, helping you keep your code error-free and reliable.