TypeScript from JavaScript Part 4 of 7: Advanced Types and Type Guards
Welcome back to our series on transitioning from JavaScript to TypeScript! If you’ve been following along, by now you should have a good grasp on the basics of TypeScript, including modules, namespaces, and decorators which we covered in the last part. In this part, we are going to dive into advanced types and type guards in TypeScript.
Remember, our goal here is not just to learn TypeScript, but also to understand how it can simplify our work, save time, and ultimately elevate our apps. So let's get started!
1. Advanced Types
TypeScript comes with a set of advanced types that help you to model your data more precisely. Let's go through some of the most useful ones.
a. Union Types
Union types allow you to define a variable that can be of multiple types. You can define a union type using the |
operator.
let value: number | string;
value = 10;
value = 'Hello';
In this example, the value
variable can be either a number
or a string
.
b. Intersection Types
Intersection types allow you to combine multiple types into one. You can define an intersection type using the &
operator.
interface Person {
name: string;
}
interface Employee {
id: number;
}
type EmployeeRecord = Person & Employee;
const employee: EmployeeRecord = {
name: 'Alice',
id: 1
};
In this example, the EmployeeRecord
type is an intersection of the Person
and Employee
types.
c. Literal Types
Literal types allow you to restrict a variable to a specific value.
type Direction = 'north' | 'south' | 'east' | 'west';
const direction: Direction = 'north';
In this example, the direction
variable can only be one of the four specified values.
d. Mapped Types
Mapped types allow you to create a new type from an existing type by transforming its properties.
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
interface Person {
name: string;
age: number;
}
type ReadonlyPerson = Readonly<Person>;
const person: ReadonlyPerson = {
name: 'Alice',
age: 30
};
person.name = 'Bob'; // Error
In this example, the ReadonlyPerson
type is a mapped type that makes all properties of the Person
type read-only.
e. Conditional Types
Conditional types allow you to define a type based on a condition.
type IsString<T> = T extends string ? true : false;
type A = IsString<string>; // true
type B = IsString<number>; // false
In this example, the IsString
type is a conditional type that checks if a type is string
.
2. Type Guards
Type guards are expressions that perform a runtime check on the type of a variable.
a. typeof Type Guards
The typeof
operator can be used as a type guard to check the type of a variable.
function print(value: number | string) {
if (typeof value === 'number') {
console.log(value.toFixed(2));
} else {
console.log(value.toUpperCase());
}
}
print(10); // 10.00
print('hello'); // HELLO
In this example, the typeof
operator is used as a type guard to check if the value
variable is a number
or a string
.
b. instanceof Type Guards
The instanceof
operator can be used as a type guard to check the instance of a class.
class Circle {
radius: number;
constructor(radius: number) {
this.radius = radius;
}
}
class Square {
side: number;
constructor(side: number) {
this.side = side;
}
}
function getArea(shape: Circle | Square) {
if (shape instanceof Circle) {
return Math.PI * Math.pow(shape.radius, 2);
} else {
return Math.pow(shape.side, 2);
}
}
const circle = new Circle(5);
const square = new Square(5);
console.log(getArea(circle)); // 78.53981633974483
console.log(getArea(square)); // 25
In this example, the instanceof
operator is used as a type guard to check if the shape
variable is an instance of Circle
or Square
.
c. User-Defined Type Guards
You can also create your own type guards by defining a function that returns a type predicate.
interface Person {
name: string;
}
function isPerson(value: any): value is Person {
return value && typeof value.name === 'string';
}
function printName(value: any) {
if (isPerson(value)) {
console.log(value.name);
} else {
console.log('Not a person');
}
}
const person = { name: 'Alice' };
const notPerson = { age: 30 };
printName(person); // Alice
printName(notPerson); // Not a person
In this example, the isPerson
function is a user-defined type guard that checks if a value is a Person
.
Conclusion
In this part, we learned about advanced types and type guards in TypeScript. These features are extremely powerful and allow you to model your data more precisely and perform runtime type checks.
In the next part of this series, we will learn about async/await and error handling in TypeScript. Stay tuned!
That's all for now, happy coding!