Advanced TypeScript Types and Techniques
In the previous articles, we covered basic TypeScript types, interfaces, classes, generics, and modules. In this article, we will explore advanced TypeScript types and techniques that can help you write more powerful and flexible code. We will discuss type guards, discriminated unions, mapped types, and conditional types.
Type Guards
Type guards are a way to narrow down the type of a value within a specific scope. They can help you write safer code by ensuring that a value is of a specific type before performing an operation on it. There are several ways to create type guards, including using the typeof
and instanceof
operators, as well as user-defined type guards.
Here's an example of using type guards:
function isString(value: any): value is string {
return typeof value === "string";
}
function processValue(value: string | number): string {
if (isString(value)) {
// TypeScript now knows that 'value' is a string
return value.toUpperCase();
} else {
// TypeScript knows that 'value' is a number
return value.toFixed(2);
}
}
console.log(processValue("hello")); // Output: 'HELLO'
console.log(processValue(42)); // Output: '42.00'
Discriminated Unions
Discriminated unions are a technique for creating a union type where each type in the union has a unique property that can be used to discriminate between the types. This makes it easier to work with union types and enables better type checking and type inference.
Here's an example of using discriminated unions:
interface Circle {
kind: "circle";
radius: number;
}
interface Square {
kind: "square";
sideLength: number;
}
type Shape = Circle | Square;
function getArea(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius * shape.radius;
case "square":
return shape.sideLength * shape.sideLength;
}
}
const circle: Circle = { kind: "circle", radius: 5 };
const square: Square = { kind: "square", sideLength: 4 };
console.log(getArea(circle)); // Output: 78.53981633974483
console.log(getArea(square)); // Output: 16
Mapped Types
Mapped types are a way to create new types by transforming the properties of an existing type. They can be useful for creating utility types, such as making all properties optional or readonly.
Here's an example of using mapped types:
type Readonly = {
readonly [P in keyof T]: T[P];
};
interface Person {
name: string;
age: number;
}
type ReadonlyPerson = Readonly;
const person: ReadonlyPerson = {
name: "John",
age: 30
};
// person.name = "Jane"; // Error: Cannot assign to 'name' because it is a read-only property
Conditional Types
Conditional types are a way to create a new type based on a condition. They use the ternary operator syntax and can be useful for creating more complex utility types and type transformations.
Here's an example of using conditional types:
type NonNullable = T extends null | undefined ? never : T;
type NullableString = string | null | undefined;
type NonNullableString = NonNullable;
const nullable: NullableString = null;
const nonNullable: NonNullableString = "hello";
// const invalid: NonNullableString = null; // Error: Type 'null' is not assignable to type 'string'
Conclusion
In this article, we covered advanced TypeScript types and techniques, including type guards, discriminated unions, mapped types, and conditional types. These advanced features can help you write more powerful, flexible, and safer TypeScript code. In the next article, we will discuss TypeScript decorators and their use cases.
Table of Contents: Typescript for Beginners
- An Introduction to TypeScript: JavaScript's Powerful Superset
- Understanding TypeScript Types
- Working with TypeScript Interfaces
- Mastering TypeScript Classes and Inheritance
- TypeScript Generics: Flexible and Type-Safe Code
- Organizing Code with TypeScript Modules
- Advanced TypeScript Types and Techniques
- Using TypeScript Decorators to Enhance Your Code
- Configuring the TypeScript Compiler for Your Project
- TypeScript Best Practices for Cleaner and More Maintainable Code