Pick, Omit и Record
Из полной анкеты генератор умеет делать и короткие карточки. Для списка пользователей на сайте нужны только номер и имя, а для публичной страницы — всё, кроме почты. Можно описать каждую карточку своим интерфейсом, но тогда поля снова записаны в нескольких местах. Поменяете тип поля в User — и придётся искать все копии.
Утилитные типы Pick и Omit вырезают из готового типа нужную часть, а Record собирает объектный тип из списка ключей. В конце урока — ReturnType, который берёт тип результата у функции.
Pick и Omit
Оба принимают тип и имена его свойств, но делают противоположное:
interface User {
id: number;
name: string;
email: string;
age: number;
}
type Card = Pick<User, "id" | "name">;
type PublicUser = Omit<User, "email">;
const card: Card = { id: 1, name: "Ann" };
const shown: PublicUser = { id: 1, name: "Ann", age: 30 };
console.log(card.name, shown.age); // Ann 30Pick
Pick<User, "id" | "name"> оставляет только перечисленные свойства: получается тип { id: number; name: string }. Имена передают объединением литералов, и TypeScript проверяет каждое. Назвать можно только настоящее свойство User, а на Pick<User, "phone"> tsc ответит ошибкой:
error TS2344: Type '"phone"' does not satisfy the constraint 'keyof User'.В определении Pick стоит знакомое ограничение K extends keyof T.
Omit
Omit<User, "email"> делает наоборот: убирает перечисленные свойства и оставляет все остальные — id, name и age. Его удобно брать, когда убрать нужно меньше, чем оставить.
Оба типа следят за исходным. Поменяйте тип name в User, и он поменяется и в Card, и в PublicUser. Чем Pick отличается от Omit, любят спрашивать на собеседовании.
Record
Record<K, V> строит объект по списку ключей: каждый ключ из K становится свойством со значением типа V. Так удобно описывать таблицы, например переводы, где для каждого языка нужна строка:
const greetings: Record<"ru" | "en", string> = {
ru: "Привет",
en: "Hello",
};
const stock: Record<string, number> = { apple: 5 };
console.log(greetings.en, stock["apple"]); // Hello 5Давайте разберём пример по строкам:
Record<"ru" | "en", string>— объект со свойствамиruиen, и в обоих строки.- Оба свойства обязательные: tsc проверит, что ни один язык не забыт.
Record<string, number>— ключ любая строка, значение число. Это та же индексная сигнатура{ [key: string]: number }, только записанная короче.- Программа печатает
Hello 5.
Если забыть язык, например написать только { ru: "Привет" }, tsc назовёт пропущенный ключ. В сообщении TypeScript 7 печатает ключи отсортированными, поэтому "en" стоит первым:
error TS2741: Property 'en' is missing in type '{ ru: string; }' but required in type 'Record<"en" | "ru", string>'.ReturnType: тип результата
Ещё одна утилита берёт тип у функции. ReturnType<F> — тип того, что возвращает функция типа F. Утилите нужен тип функции, а не сама функция, поэтому перед именем стоит typeof:
function parseUser(line: string) {
const parts = line.split(",");
return { name: parts[0], age: Number(parts[1]) };
}
type ParsedUser = ReturnType<typeof parseUser>;
const p: ParsedUser = parseUser("Ann,30");
console.log(p.name, p.age); // Ann 30Разберём, откуда взялся тип ParsedUser:
- Для результата
parseUserTypeScript выводит тип{ name: string; age: number; }. typeof parseUser— тип самой функции:(line: string) => { name: string; age: number; }.ReturnTypeдостаёт из этого типа тип результата. ОписыватьParsedUserруками не пришлось.
Если забыть typeof, tsc подскажет, чего не хватает:
error TS2749: 'parseUser' refers to a value, but is being used as a type here. Did you mean 'typeof parseUser'?Частая ошибка
В Omit опечатались в имени свойства:
interface User {
id: number;
name: string;
email: string;
}
type Preview = Omit<User, "emial">;
const p: Preview = { id: 1, name: "Ann" }; // ✗error TS2741: Property 'email' is missing in type '{ id: number; name: string; }' but required in type 'Preview'.На строке с Omit tsc молчит. В отличие от Pick, Omit не проверяет имена: его ограничение пропускает любую строку, число или symbol, а не только keyof T. Свойства emial в User нет, убирать оказалось нечего, и email остался в Preview. Ошибка всплыла дальше, у объекта без почты. Если tsc требует свойство, которое вы убрали через Omit, проверьте имя в самом Omit.
Главное
Pick<User, "id" | "name">оставляет только названные свойства,Omit<User, "email">— все, кроме названных.Record<"ru" | "en", string>— объект, где каждый ключ из объединения обязателен и хранитstring.ReturnType<typeof f>— тип результата функцииf;typeofнужен, потому что утилите нужен тип, а не значение.Pickпроверяет имена свойств, аOmit— нет: опечатка вOmitпроходит молча.