number
, string
, and boolean
are well - known, TypeScript also offers powerful advanced type manipulation features. These features allow developers to create more flexible, reusable, and type - safe code. In this blog, we will explore the fundamental concepts, usage methods, common practices, and best practices of TypeScript advanced type manipulation tricks.Mapped types allow you to create new types by transforming each property in an existing type. They are similar to map
function in JavaScript arrays, but for types.
// Example of a simple mapped type
type ReadonlyType<T> = {
readonly [P in keyof T]: T[P];
};
interface User {
name: string;
age: number;
}
type ReadonlyUser = ReadonlyType<User>;
Conditional types allow you to choose one type based on a condition. They have a syntax similar to the ternary operator in JavaScript.
// Example of a conditional type
type IsString<T> = T extends string? true : false;
type Result1 = IsString<string>; // true
type Result2 = IsString<number>; // false
Template literal types allow you to create new string types by interpolating other types. They are similar to template literals in JavaScript.
// Example of a template literal type
type Prefix<T> = `prefix-${T}`;
type NewType = Prefix<'value'>; // "prefix-value"
Mapped types can be used to transform properties of an existing type. For example, making all properties optional.
type OptionalType<T> = {
[P in keyof T]?: T[P];
};
interface Book {
title: string;
author: string;
year: number;
}
type OptionalBook = OptionalType<Book>;
Conditional types can be used to implement conditional logic in types. For example, filtering out properties of a certain type.
type FilterProperties<T, U> = {
[P in keyof T as T[P] extends U? P : never]: T[P];
};
interface Example {
name: string;
age: number;
isAdmin: boolean;
}
type StringProperties = FilterProperties<Example, string>; // { name: string }
Template literal types can be used for string manipulation. For example, creating event names based on an action.
type EventName<T extends string> = `${T}Event`;
type ClickEvent = EventName<'click'>; // "clickEvent"
As shown earlier, mapped types can be used to create read - only variants of existing types.
type ReadonlyVariant<T> = {
readonly [P in keyof T]: T[P];
};
interface Config {
apiKey: string;
baseUrl: string;
}
type ReadonlyConfig = ReadonlyVariant<Config>;
Conditional types can be used to filter properties based on their type.
type NumberProperties<T> = {
[P in keyof T as T[P] extends number? P : never]: T[P];
};
interface Data {
id: number;
name: string;
price: number;
}
type OnlyNumbers = NumberProperties<Data>; // { id: number; price: number }
You can use the typeof
operator along with template literal types to generate union types from arrays.
const colors = ['red', 'green', 'blue'] as const;
type ColorUnion = typeof colors[number]; // "red" | "green" | "blue"
Complex type manipulations can make the code hard to understand and maintain. Try to break down complex type operations into smaller, more manageable steps.
Since type manipulation can be hard to understand at a glance, it’s important to document the purpose and functionality of type manipulations clearly. Use comments to explain what each part of the type manipulation is doing.
Just like regular code, type manipulations should be tested. You can use tools like tsd
to test types in TypeScript projects.
TypeScript advanced type manipulation tricks offer a powerful set of tools for creating more flexible, reusable, and type - safe code. Mapped types, conditional types, and template literal types are just a few examples of the advanced features that TypeScript provides. By understanding the fundamental concepts, usage methods, common practices, and best practices, developers can leverage these features to write high - quality TypeScript code.