Article
To Cast or Not to Cast: A TypeScript Dilemma
October 23, 2023
As programmers we sometimes run into problems that require less than ideal solutions. These outcomes may not feel great, but might be necessary in order to move forward. When it comes to type casting in TypeScript, it can often feel like we are taking a less than ideal approach by forcing a type that we want. But are there instances where this might be a helpful strategy? Let’s take a look!
What is type casting?
Type casting is the process of explicitly converting the type of a variable or piece of data to another type without changing its underlying value. When we do this, we are essentially telling TypeScript that we know more about the intent behind a variable’s type than it is inferring from the code. This gives developers the ability to manually tell TypeScript what the type is and disable TypeScript from making type checks on its value.
This can be done using the as
keyword:
const variable: any = "I am a string";
const string = variable as string;
Or by using angle brackets <...>
:
const variable: any = "I am a string";
const string = <string>variable;
With great power…
Type casting is a powerful tool that gives developers more control over types in an application. However, if a casted type does not match the value of a variable then this could lead to errors or unexpected behavior. Therefore as developers, we need to be very careful and intentional when using type casting.
Casting as the wrong type is allowed, but can prove dangerous when referencing that variable because TypeScript checks are overridden:
type Cat = {
name: string;
age: number;
}
type Dog = {
name: string;
}
const dogExample = {
name: "Fido",
} as Cat;
const dogAge = dogExample.age; // oops!
Type assertions are safer
The risk involved with casting is why typing with assertions is generally preferred. By typing a variable using a colon, TypeScript will run checks against the value:
Property ‘age’ is missing in type ‘{ name: string; }’ but required in type ‘Cat’type Cat = {
name: string;
age: number;
}
type Dog = {
name: string;
}
const dogExample: Cat = {
name: "Fido",
};
When can casting be helpful?
Refining types
As a variable or value evolves, the originally assigned type might not be totally accurate anymore. Refining the type at this point can add further type protection and insight into the intent behind the data.
type Cat = {
name: string;
age: number;
}
type Dog = {
name: string;
}
type Animal = Cat | Dog;
const animalExample: Animal = {
name: "Fido"
}
const dogExample = animalExample as Dog;
Unknown sources
Sometimes TypeScript cannot precisely infer types from certain sources that may be unknown or generic. For example, querying an element in the DOM will give you a type of Element | null
, but we can be more specific if we know more about the element.
const divExample = document.querySelector('#div') as HTMLDivElement;
Test data
Defining data in tests is like it suggests — for testing purposes. This might mean that we want to provide incomplete or varying data for testing edge case scenarios. However, we still need to satisfy TypeScript’s type checks and this is where casting can lend a hand. In this example, the data
prop expects a type of ComponentData
, but we are passing an empty object for testing purposes.
test("component has no data", () => {
render(
<Component data={{} as ComponentData} />
);
});
Summary
Type assertions are preferred when defining TypeScript types. But in certain situations, type casting can help refine or alter types to aid developers in specificity and in handling edge cases. This gives developers the power to work with types from variables and data in a more precise and flexible manner.
Type casting can provide valuable context to other developers and help shed light on the intent behind a variable or piece of data. However, this approach should be used responsibly because it overrides TypeScript’s type checks. Cheers to type safety!
Rob writes type-safe code at Livefront .