Утилитарные типы: Partial, Required, Pick, Omit
Встроенные типы для трансформации типов данных
Цели урока
- Разобрать утилитарный тип Partial<T>
- Разобрать утилитарный тип Required<T>
- Разобрать утилитарный тип Pick<T, K>
- Разобрать утилитарный тип Omit<T, K>
- Научиться комбинировать утилитарные типы
Что такое утилитарные типы?
TypeScript поставляется с набором встроенных утилитарных типов, которые позволяют трансформировать существующие типы. Они работают как функции для типов: принимают один тип и возвращают новый.
Утилитарные типы — как функции-трансформеры для данных, только на уровне типов. Они берут «сырой» тип и создают на его основе новый, изменённый тип.
Все утилитарные типы определены в стандартной библиотеке TypeScript. Вы можете использовать их прямо сейчас:
// Но вы можете использовать их без дополнительных импортов:
type Partial<T> = { [P in keyof T]?: T[P] };
type Required<T> = { [P in keyof T]-?: T[P] };
type Pick<T, K extends keyof T> = { [P in K]: T[P] };
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
Partial<T> — все свойства необязательные
Partial<T> создаёт тип, где все свойства оригинального типа становятся необязательными:
interface User {
name: string;
age: number;
email: string;
}
// Partial делает все свойства необязательными
type PartialUser = Partial<User>;
// Эквивалентно:
// type PartialUser = {
// name?: string;
// age?: number;
// email?: string;
// };
// Теперь можно создать объект с любым подмножеством свойств:
const partialUser1: PartialUser = { name: "Алиса" }; // OK!
const partialUser2: PartialUser = { age: 25 }; // OK!
const partialUser3: PartialUser = {}; // OK!
Partial удобен для:
- Функций обновления (update): передаём только изменённые поля
- Функций поиска (filter): указываем только нужные критерии
- Дефолтных значений: задаём только часть полей
function updateUser(
id: number,
updates: Partial<User>
): void {
// Обновляем только переданные поля
console.log(`Обновление пользователя ${id}`, updates);
}
// Можно обновить любое подмножество полей:
updateUser(1, { name: "Новое имя" }); // OK!
updateUser(1, { age: 30, email: "new@email.com" }); // OK!
updateUser(1, {}); // OK! Ничего не меняем
Required<T> — все свойства обязательные
Required<T> — противоположность Partial. Он делает все свойства обязательными, убирая модификатор ?:
interface Config {
host?: string;
port?: number;
debug?: boolean;
}
// Required делает все свойства обязательными
type FullConfig = Required<Config>;
// Эквивалентно:
// type FullConfig = {
// host: string;
// port: number;
// debug: boolean;
// };
// Теперь все свойства обязательны:
const config1: FullConfig = {
host: "localhost",
port: 3000,
debug: true
}; // OK!
// Ошибка: отсутствует debug:
// const config2: FullConfig = { host: "localhost", port: 3000 }; // Ошибка!
Required удобен, когда:
- Вы хотите гарантировать, что все поля заполнены
- Вы работаете с конфигурацией, где все параметры критичны
- Вы хотите создать строгую версию интерфейса
Pick<T, K> — выбор свойств
Pick<T, K> создаёт тип, содержащий только выбранные свойства из оригинального типа:
interface User {
name: string;
age: number;
email: string;
password: string;
}
// Pick выбирает только name и email
type UserPublic = Pick<User, "name" | "email">;
// Эквивалентно:
// type UserPublic = {
// name: string;
// email: string;
// };
// Использование:
const publicUser: UserPublic = {
name: "Алиса",
email: "a@b.com"
}; // OK!
// Ошибка: password не входит в тип UserPublic:
// const user2: UserPublic = {
// name: "Алиса",
// email: "a@b.com",
// password: "secret" // Ошибка!
// };
Pick удобен для:
- Создания публичных версий интерфейсов (без секретных полей)
- Форм: нужна только часть полей
- API: передаём только нужные данные
- Пропсов компонентов: только нужные свойства
interface FormData {
name: string;
email: string;
password: string;
confirmPassword: string;
}
// Для формы входа нужны только email и password
type LoginForm = Pick<FormData, "email" | "password">;
// Для формы регистрации нужны все поля
type RegisterForm = Pick<FormData, "name" | "email" | "password" | "confirmPassword">;
Omit<T, K> — исключение свойств
Omit<T, K> — противоположность Pick. Он создаёт тип, исключая указанные свойства:
interface User {
id: number;
name: string;
email: string;
createdAt: Date;
}
// Omit исключает id и createdAt
type CreateUser = Omit<User, "id" | "createdAt">;
// Эквивалентно:
// type CreateUser = {
// name: string;
// email: string;
// };
// Использование:
function createUser(data: CreateUser): User {
return {
...data,
id: Date.now(),
createdAt: new Date()
};
}
// Вызываем только с нужными полями:
createUser({ name: "Алиса", email: "a@b.com" }); // OK!
Omit удобен для:
- Функций создания: исключаем id, createdAt, updatedAt
- Функций обновления: исключаем неизменяемые поля
- Пропсов компонентов: исключаем внутренние свойства
Комбинирование утилитарных типов
Утилитарные типы можно комбинировать друг с другом для создания сложных трансформаций:
interface Product {
id: number;
name: string;
price: number;
description: string;
createdAt: Date;
}
// 1. Исключаем id и createdAt
// 2. Делаем все свойства необязательными
type UpdateProduct = Partial<Omit<Product, "id" | "createdAt">>;
// Эквивалентно:
// type UpdateProduct = {
// name?: string;
// price?: number;
// description?: string;
// };
// Использование:
function updateProduct(id: number, updates: UpdateProduct): void {
console.log(`Обновление продукта ${id}`, updates);
}
updateProduct(1, { price: 100 }); // OK!
updateProduct(1, { name: "Название", description: "Описание" }); // OK!
Практический пример: REST API
Покажем, как утилитарные типы помогают в типизации REST API:
interface Article {
id: number;
title: string;
content: string;
authorId: number;
createdAt: Date;
updatedAt: Date;
}
// Типы для разных HTTP-методов:
// GET /articles — ответ
type ArticleResponse = Article;
// POST /articles — тело запроса (без id, createdAt, updatedAt)
type CreateArticle = Omit<Article, "id" | "createdAt" | "updatedAt">;
// PATCH /articles/:id — тело запроса (обновление, все поля необязательны)
type UpdateArticle = Partial<Omit<Article, "id" | "createdAt" | "updatedAt">>;
// GET /articles/:id — ответ
type ArticleDetail = Article;
// Публичный просмотр (без authorId)
type ArticlePublic = Pick<Article, "id" | "title" | "content">;
Практический пример: пропсы компонента
В React утилитарные типы часто используются для типизации пропсов:
interface ButtonProps {
label: string;
onClick: () => void;
variant?: "primary" | "secondary" | "danger";
size?: "small" | "medium" | "large";
disabled?: boolean;
}
// Disabled кнопка: onClick и disabled не нужны
type DisabledButtonProps = Omit<ButtonProps, "onClick" | "disabled"> & {
disabled: true;
};
// Loading кнопка: нужен только label
type LoadingButtonProps = Pick<ButtonProps, "label" | "size"> & {
loading: boolean;
};
Сводная таблица
| Тип | Описание | Пример |
| Partial<T> | Все свойства необязательны | { name?: string; age?: number } |
| Required<T> | Все свойства обязательны | { name: string; age: number } |
| Pick<T, K> | Только выбранные свойства | Pick<User, 'name' | 'email'> |
| Omit<T, K> | Все свойства кроме исключённых | Omit<User, 'password'> |
Итоги урока
- Partial<T> — делает все свойства необязательными
- Required<T> — делает все свойства обязательными
- Pick<T, K> — выбирает только указанные свойства
- Omit<T, K> — исключает указанные свойства
- Утилитарные типы можно комбинировать
- Они определены в стандартной библиотеке TypeScript
- Используйте их для типизации API, пропсов, форм
Практический пример: типобезопасная валидация
Утилитарные типы помогают создавать типобезопасные функции валидации:
interface RegistrationForm {
name: string;
email: string;
password: string;
confirmPassword: string;
age: number;
newsletter: boolean;
}
// Типы ошибок валидации
type ValidationErrors = Partial<Record<keyof RegistrationForm, string>>;
// Функция валидации
function validate(
data: Partial<RegistrationForm>
): ValidationErrors {
const errors: ValidationErrors = {};
if (!data.name) errors.name = "Обязательное поле";
if (!data.email) errors.email = "Обязательное поле";
if (!data.password) errors.password = "Обязательное поле";
return errors;
}
// Использование:
const errors = validate({ name: "Алиса" });
// errors.email = "Обязательное поле"
// errors.password = "Обязательное поле"
Практический пример: типизированный store
Создадим простой типизированный store с использованием Partial и Pick:
interface Todo {
id: number;
title: string;
completed: boolean;
createdAt: Date;
}
// Тип для создания (без id и createdAt)
type CreateTodo = Omit<Todo, "id" | "createdAt">;
// Тип для обновления (все поля необязательны)
type UpdateTodo = Partial<Omit<Todo, "id" | "createdAt">>;
// Тип для фильтра (только title и completed)
type TodoFilter = Pick<Todo, "title" | "completed">;
// Store
class TodoStore {
private todos: Todo[] = [];
create(data: CreateTodo): Todo {
const todo: Todo = {
...data,
id: Date.now(),
createdAt: new Date()
};
this.todos.push(todo);
return todo;
}
update(id: number, data: UpdateTodo): void {
// Обновляем только переданные поля
}
find(filter: TodoFilter): Todo[] {
// Ищем по критериям
return this.todos.filter(todo => {
if (filter.title && !todo.title.includes(filter.title)) return false;
if (filter.completed !== undefined && todo.completed !== filter.completed) return false;
return true;
});
}
}
Практический пример: типобезопасные пропсы
Покажем, как утилитарные типы помогают в типизации React-компонентов:
interface InputProps {
label: string;
value: string;
onChange: (value: string) => void;
placeholder?: string;
disabled?: boolean;
error?: string;
type?: "text" | "password" | "email";
}
// Пропсы для числового ввода
type NumberInputProps = Omit<InputProps, "value" | "type"> & {
value: number;
onChange: (value: number) => void;
min?: number;
max?: number;
};
// Пропсы для textarea
type TextareaProps = Omit<InputProps, "type"> & {
rows?: number;
maxLength?: number;
};
Распространённые ошибки
Вот частые ошибки при использовании утилитарных типов:
// Pick<User, "name" | "email"> — только name и email
// Omit<User, "password"> — всё кроме password
// НЕ путайте: Pick берёт, Omit убирает
// Если нужны необязательные поля — используйте ?
// interface User { name: string; age?: number; }
// Partial подходит для функций обновления
// Partial<Pick<Omit<User, "id">, "name" | "email">>
// Проще: создать отдельный интерфейс
// interface UserUpdate { name?: string; email?: string; }
Следующий урок
Утилитарные типы: Record, Readonly, Exclude, Extract — разберём оставшиеся встроенные типы
Тест по утилитарным типам
8 вопросов