
Build 10x products in minutes by chatting with AI - beyond just a prototype.
Topics
Utility types in TypeScript are a powerful feature that facilitates common type transformations. They provide developers with tools to manipulate types, from transforming existing ones to creating entirely new ones. These utility types are part of TypeScript's type system, designed to enhance code quality and developer productivity by enabling type checking at compile time.
The TypeScript Pick utility type is a construct that allows developers to create a new type by selecting specific properties from an existing type. It's a way to construct types with only a subset of the properties of another type, which can be helpful when you want to limit the properties that can be accessed on a particular object.
interface Person { name: string; age: number; email: string; } type PickedPerson = Pick<Person, 'name' | 'email'>;
In the above example, PickedPerson is a new type with only the Person interface's name and email properties.
The basic syntax of the Pick utility type is straightforward. It requires two parameters: the type you are picking from and the keys of the properties you want to pick.
type Pick<T, K extends keyof T> = { [P in K]: T[P]; };
Here's a practical code example demonstrating how to use Pick:
interface User { id: number; name: string; age: number; email: string; } // Using Pick to create a new type with only 'name' and 'age' properties type UserNameAndAge = Pick<User, 'name' | 'age'>; // This object is now constrained to the UserNameAndAge type const user: UserNameAndAge = { name: 'Alice', age: 30 };
In this example, UserNameAndAge is a new type that includes only the name and age properties from the User interface. The user object is then defined using this new type, ensuring it only contains the specified properties.
Creating a new type with Pick is a common task when creating a new object type that includes only a few properties from an existing type. This can help create more specific types for certain operations, reducing the possibility of passing unnecessary data around or exposing sensitive information.
Here's an example of how to create a new type using Pick:
interface Employee { name: string; position: string; salary: number; startDate: Date; } // Creating a new type for public employee profiles type PublicEmployeeProfile = Pick<Employee, 'name' | 'position'>; // Public profile for an employee const publicProfile: PublicEmployeeProfile = { name: 'John Doe', position: 'Software Developer' };
In this code snippet, PublicEmployeeProfile is a new type that only includes the name and position properties from the Employee interface, which might be the only information you want to display in a public setting.
When working with TypeScript, it's essential to understand the differences between the Pick and Partial utility types. Both are used to create new types, but they serve different purposes.
Pick allows you to create a new type by selecting specific properties from an existing type. On the other hand, Partial makes all properties of the given type optional, which means you can provide any subset of properties.
Here's an example to illustrate the difference:
interface Task { title: string; description: string; completed: boolean; } type PickedTask = Pick<Task, 'title' | 'completed'>; type PartialTask = Partial<Task>; const task1: PickedTask = { title: 'Learn TypeScript', completed: false }; const task2: PartialTask = { title: 'Learn TypeScript' // 'description' and 'completed' are optional };
In this example, PickedTask is a new type with only the title and completed properties, while PartialTask is a type where all properties are optional.
Pick and Omit are two utility types somewhat opposite in their functionality. While Pick is used to select certain properties to create a new type, Omit is used to exclude certain properties from a type.
Here's an example showing how to use Omit:
SafeUser is a new type in this code that includes all properties from the User interface except for password. This is useful to ensure sensitive data is not included in an object.
Pick can also be used with nested objects and interfaces to select properties at a deeper level. This requires a more advanced understanding of TypeScript's type system.
Here's an example of selecting properties from nested objects using Pick:
In this example, CityContact is a new type that includes the phone property from the Contact interface and only the city property from the nested address object.
Pick can be particularly powerful when used in conjunction with generics. This allows you to create reusable and dynamic types that can adapt based on the input provided.
Here's a code example showing Pick used with generics:
In this function, getProperty, T represents the object type, and K is the key type constrained to the keys of T. The function returns the value of the specified property key from the object.
Pick is handy when limiting the properties passed through your application. For instance, when creating view models for client-side applications, you should exclude certain properties containing sensitive data or irrelevant to the view.
Here's an example of using Pick to handle sensitive data by omitting certain keys:
In this example, AccountDetails is a new type that includes only the non-sensitive properties from the Account interface, ensuring the password is not exposed.
Pick can be combined with other utility types like Readonly or Record to create more complex or specific types.
Here's an example of creating a read-only pick type:
In this code, ReadOnlyProduct is a new type that includes only the name and price properties from the Product interface, and it makes them read-only, preventing any modifications.
Pick can be used with union types and function types to create new types more specific to the context in which they are used.
Here's an example of creating a new type that picks properties from a union type:
In this example, ShapeKind is a new type that includes only the kind property from the Shape union type.
Pick contributes to type safety in TypeScript by ensuring that only the specified properties are used, which can prevent runtime errors due to missing or incorrect property types.
Here's an example showing how Pick ensures correct property types are used:
In this code, the createBox function requires an object with only width and height properties, ensuring that the correct types are passed to the function.
While the Pick utility type is powerful, there are limitations and considerations to consider. One limitation is that Pick cannot be used to pick properties from types where the keys are not known ahead of time, such as index signatures.
Additionally, overusing Pick can lead to a proliferation of small types, which can clutter the codebase and make it harder to maintain. It's important to balance the use of Pick with the need for clarity and simplicity in your type definitions.
To use Pick effectively in TypeScript projects, consider the following best practices:
Here's an example illustrating best practices in action:
In this example, UserDisplayInfo is a well-defined type representing the necessary information for displaying a user while avoiding creating particular types for each property.
The Pick utility type is valuable in the TypeScript developer's toolkit. It allows for creating precise types by selecting specific properties from existing types, contributing to cleaner, more maintainable, and more readable code.
By understanding and applying Pick appropriately, along with other utility types, developers can ensure that their TypeScript codebases remain robust, scalable, and easy to work with. Remember to use Pick judiciously and with other TypeScript features to get the most out of this powerful language feature.
interface User {
id: number;
name: string;
age: number;
password: string;
}
// Using Omit to create a new type without the 'password' property
type SafeUser = Omit<User, 'password'>;
const userWithoutPassword: SafeUser = {
id: 1,
name: 'Alice',
age: 30
// 'password' is omitted from this type
};
interface Contact {
phone: string;
address: {
street: string;
city: string;
zipCode: string;
};
}
// Using Pick to create a type with only the 'city' property from the nested 'address' object
type CityContact = Pick<Contact, 'phone' | 'address'> & {
address: Pick<Contact['address'], 'city'>
};
const contactWithCity: CityContact = {
phone: '123-456-7890',
address: {
city: 'Springfield'
}
};
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const userData = {
name: 'John',
age: 25,
email: 'john@example.com'
};
// Using the generic function to get the value of 'name'
const userName = getProperty(userData, 'name');
interface Account {
id: number;
username: string;
password: string; // Sensitive data
email: string;
}
// Creating a type for account details without sensitive data
type AccountDetails = Pick<Account, 'id' | 'username' | 'email'>;
const accountDetails: AccountDetails = {
id: 1,
username: 'john_doe',
email: 'john.doe@example.com'
// 'password' property is not included
};
interface Product {
id: number;
name: string;
price: number;
}
// Creating a read-only type for product display
type ReadOnlyProduct = Readonly<Pick<Product, 'name' | 'price'>>;
const displayProduct: ReadOnlyProduct = {
name: 'Gadget',
price: 99.99
};
// Attempting to modify the properties will result in a TypeScript error
// displayProduct.price = 79.99; // Error: Cannot assign to 'price' because it is a read-only property.
type Shape =
| { kind: 'circle'; radius: number }
| { kind: 'square'; sideLength: number };
// Creating a type that picks the 'kind' property from the Shape union type
type ShapeKind = Pick<Shape, 'kind'>;
const circleKind: ShapeKind = { kind: 'circle' };
interface Options {
width: number;
height: number;
color: string;
}
function createBox(options: Pick<Options, 'width' | 'height'>) {
// Function implementation
}
// This call is valid as only 'width' and 'height' are required
createBox({ width: 100, height: 200 });
// This call will result in a TypeScript error as 'color' is not part of the picked properties
// createBox({ width: 100, height: 200, color: 'blue' }); // Error: Object literal may only specify known properties.
interface User {
id: number;
name: string;
email: string;
role: string;
}
// Good practice: Creating a type for user display information
type UserDisplayInfo = Pick<User, 'name' | 'email'>;
// Overuse: Creating too many specific types for each component
// type UserName = Pick<User, 'name'>;
// type UserEmail = Pick<User, 'email'>;
// ... and so on for each property