TypeScript provides a way to define and enforce types in our code. However, sometimes we may have a value that can have more than one type or we may not be sure about its type at runtime. This is where type guards and type assertions come in handy.
Type guards
Type guards are a way to check the type of a value at runtime, and change the type of the value accordingly. They can be implemented using the typeof
, instanceof
, and in
operators.
typeof operator
The typeof
operator returns a string that represents the type of a value. We can use it to check if a value is a string, number, boolean, or any other primitive type.
function logLength(value: string | number) {
if (typeof value === 'string') {
console.log(value.length);
} else {
console.log(Math.abs(value));
}
}
logLength('Hello'); // Output: 5
logLength(-4); // Output: 4
instanceof operator
The instanceof
operator checks if an object is an instance of a particular class. We can use it to check if a value is an instance of a class or an interface.
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
function greet(person: Person | string) {
if (person instanceof Person) {
console.log(`Hello, ${person.name}!`);
} else {
console.log(`Hello, ${person}!`);
}
}
greet('John'); // Output: Hello, John!
greet(new Person('Sarah', 25)); // Output: Hello, Sarah!
in operator
The in
operator checks if a property exists in an object. We can use it to check if a value has a particular property.
interface Square {
kind: 'square';
size: number;
}
interface Rectangle {
kind: 'rectangle';
width: number;
height: number;
}
type Shape = Square | Rectangle;
function calculateArea(shape: Shape) {
if ('size' in shape) {
return shape.size * shape.size;
} else {
return shape.width * shape.height;
}
}
calculateArea({ kind: 'square', size: 5 }); // Output: 25
calculateArea({ kind: 'rectangle', width: 5, height: 10 }); // Output: 50
Type assertions
Type assertions are a way to tell TypeScript the type of a value, even if TypeScript cannot infer it. They can be implemented using the as
keyword or the angle-bracket syntax.
as keyword
The as
keyword is used to tell TypeScript the type of a value.
const value: any = 'Hello';
const length = (value as string).length;
console.log(length); // Output: 5
Angle-bracket syntax
The angle-bracket syntax is an alternative way to tell TypeScript the type of a value.
const value: any = 'Hello';
const length = (<string>value).length;
console.log(length); // Output: 5
Type assertion vs type coercion
It’s important to note that type assertion is not the same as type coercion. Type coercion is when a value is automatically converted from one type to another by the JavaScript engine, based on a set of rules. Type assertion, on the other hand, is a manual way to tell TypeScript the type of a value.
const value: any = ‘5’;
// Type assertion
const numberValue = value as number;
const sum = numberValue + 2;
console.log(sum); // Output: 7
// Type coercion
const result = ‘5’ + 2;
console.log(result); // Output: ’52’
If we try to add a string and a number, JavaScript will coerce the string to a number, and return the sum.
const result = '5' + 2;
console.log(result); // Output: '52'
However, if we try to add a string and a number in TypeScript, we’ll get a compilation error, because TypeScript does not allow type coercion.
const result: number = '5' + 2; // Compilation error
We can use type assertion to tell TypeScript that we know what we’re doing, and that we want the result to be a string.
const result: string = ('5' as any) + 2;
console.log(result); // Output: '52'
Conclusion
TypeScript’s type system is one of its most powerful features, and type guards and type assertions allow us to work with values whose types may not be known at compile time. Type guards allow us to change the type of a value at runtime, while type assertions allow us to tell TypeScript the type of a value, even if TypeScript cannot infer it.