Type assertions and type guards help TypeScript developers work safely with values whose types may not be known precisely.
- Type Assertions tell TypeScript to treat a value as a specific type.
- Type Guards use runtime checks to narrow a value to a more specific type.
- Type assertions do not perform runtime validation.
- Type guards are useful when working with union types and unknown values.
Type Assertions
Type assertions allow you to tell TypeScript that you know the type of a value more specifically than the compiler can determine.
Note: Type assertions only affect TypeScript's type checking. They do not change or convert the value at runtime.
Syntax
There are two syntaxes for type assertions.
1. Using the as Keyword
The as syntax is the recommended approach in most TypeScript code.
const value: unknown = "Hello, TypeScript";
const strLength: number = (value as string).length;
console.log(strLength);
Output:
16- value as string tells TypeScript to treat value as a string.
- The .length property can then be accessed.
- The actual value remains unchanged at runtime.
2. Using Angle-Bracket Syntax
TypeScript also supports angle-bracket syntax for type assertions.
const value: unknown = "Hello, TypeScript";
const strLength: number = (<string>value).length;
console.log(strLength);
Output:
16- <string>value tells TypeScript to treat value as a string.
- This syntax should not be used in JSX/TSX files because it conflicts with JSX syntax.
When to Use Type Assertions
- When TypeScript has insufficient type information: Use an assertion when you have reliable knowledge about a value's type that TypeScript cannot determine.
- When working with DOM elements: Assertions can help when you know the specific type of an element.
- When working with
unknown: An assertion can provide a more specific type when the value's type is known.
Important: Avoid using assertions simply to silence TypeScript errors. An incorrect assertion can lead to runtime errors.
Type Guards
Type guards are runtime checks that allow TypeScript to narrow a value from a broader type to a more specific type.
1. typeof Type Guard
The typeof operator can narrow primitive types such as string, number, and boolean.
function displayValue(value: number | string): void {
if (typeof value === "string") {
console.log(`String length: ${value.length}`);
} else {
console.log(`Number: ${value.toFixed(2)}`);
}
}
displayValue("Hello");
displayValue(123.456);
Output:
String length: 5
Number: 123.46
- typeof value === "string" narrows value to string.
- The else block narrows value to number.
- TypeScript allows type-specific operations after narrowing.
2. instanceof Type Guard
The instanceof operator checks whether an object is an instance of a particular class.
class Animal {
move(): void {
console.log("Moving...");
}
}
class Dog extends Animal {
bark(): void {
console.log("Woof!");
}
}
function makeSound(animal: Animal): void {
if (animal instanceof Dog) {
animal.bark();
} else {
animal.move();
}
}
makeSound(new Dog());
makeSound(new Animal());
Output:
Woof!
Moving...
- animal instanceof Dog checks whether animal is a Dog.
- TypeScript narrows animal to Dog inside the if block.
- This allows the bark() method to be called safely.
3. in Operator Type Guard
The in operator can narrow object types based on the presence of a property.
interface Car {
drive(): void;
}
interface Boat {
sail(): void;
}
function move(vehicle: Car | Boat): void {
if ("drive" in vehicle) {
vehicle.drive();
} else {
vehicle.sail();
}
}
const car: Car = {
drive: () => console.log("Car is driving")
};
move(car);
Output:
Car is driving- "drive" in vehicle checks whether the drive property exists.
- TypeScript narrows vehicle to Car inside the if block.
- The else block is narrowed to Boat.
4. User-Defined Type Guards
You can create custom type guard functions using a type predicate such as value is Type.
interface Cat {
type: "cat";
meow(): void;
}
interface Dog {
type: "dog";
bark(): void;
}
function isCat(animal: Cat | Dog): animal is Cat {
return animal.type === "cat";
}
function makeSound(animal: Cat | Dog): void {
if (isCat(animal)) {
animal.meow();
} else {
animal.bark();
}
}
const myCat: Cat = {
type: "cat",
meow: () => console.log("Meow!")
};
makeSound(myCat);
Output:
Meow!- isCat() checks whether the object represents a Cat.
- animal is Cat tells TypeScript that the function acts as a type guard.
- After isCat(animal) returns true, TypeScript treats animal as Cat.
Applications of Type Guards
- Narrow Union Types: Determine which type a value represents at runtime.
- Improve Type Safety: Allow type-specific properties and methods to be accessed safely.
- Handle Unknown Data: Help narrow values received from external sources.
- Reduce Runtime Errors: Ensure operations are performed only on values with the required structure.
Type Assertions vs Type Guards
| Type Assertions | Type Guards |
|---|---|
| Tell TypeScript to treat a value as a specific type. | Use checks to narrow a value to a specific type. |
| Do not perform runtime checks. | Perform runtime checks when using operators such as typeof, instanceof, or in. |
| Use as or angle-bracket syntax. | Use typeof, instanceof, in, or custom type predicates. |
The developer is responsible for ensuring the assertion is correct. | TypeScript narrows the type based on the result of the check. |
Useful when you already know more about a value's type. | Useful when the type depends on the value at runtime. |
Incorrect assertions can lead to runtime errors. | Runtime checks help make operations safer. |