Typescript: Сопоставление типов (Mapped Types)
Чтобы при работе с объектными типами избежать дублирования полей и переиспользовать существующие, мы можем использовать механизм поиска типов — Lookup Types:
interface Person {
name: string;
age: number;
location?: {
country: string;
city: string;
};
}
interface PersonDetails {
location: Person['location'];
}
Конструкция Type[Key]
выглядит и работает так же, как получение значения объекта по ключу в JavaScript. Но доступ через точку тут не сработает.
Lookup Types позволяет также получить объединение типов из объекта по нескольким известным ключам, объединенным с помощью вертикальной черты |
:
type User = {
id: number;
name: string;
email: string;
}
type UserFields = User['id' | 'name' | 'email']; // string | number
Чтобы получить объединение всех ключей из объекта, мы можем использовать оператор keyof
.
Упростим наш пример:
type User = {
id: number;
name: string;
email: string;
}
type UserFields = User[keyof User]; // string | number
Чтобы не дублировать полностью все поля одного объектного типа, в другом используются вспомогательные типы:
Pick<Type, Keys>
— создает объектный тип с ключамиKeys
изType
Omit<Type, Keys>
— создает объектный тип, из которого исключаются ключиKeys
изType
interface Person {
name: string;
age: number;
location?: string;
}
const details: Pick<Person, 'name' | 'age'> = {
name: 'John',
age: 42,
};
const details2: Omit<Person, 'location'> = {
name: 'John',
age: 42,
};
В этом примере тип, полученный в результате Pick<Person, 'name' | 'age'>
и Omit<Person, 'location'>
, будет одним и тем же.
Все Utility Types в TypeScript написаны при помощи встроенных конструкций. Мы уже изучили достаточно концепций TypeScript, чтобы начать с ними разбираться. Поэтому мы можем задаться вопросом, как они реализованы.
Рассмотрим, как реализован тип Pick
:
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
Pick<T, K>
— дженерик тип с двумя параметрами: T
и K
. На K
мы также наложили ограничение extends keyof T
. Это означает, что K
должно содержать перечисление ключей из T
.
Оператор in
здесь работает таким же образом, как в обычных циклах: выполняется перебор по всем элементам перечисления. В примере выше происходит перебор всех элементов в K
. На каждой итерации P
будет содержать текущий элемент из K
. Каждый такой элемент P
становится ключом, а для значения мы ищем подходящий тип в объектном типе T[P]
.
Операторы keyof
(Lookup Types) и in
(Mapped Types) часто идут рядом. С помощью keyof
мы получаем доступ ко всем именам свойств объектного типа, а благодаря in
можем циклически пройти по всем свойствам. Эти операции являются ключевыми при создании своих вспомогательных типов при работе с объектными типами данных.
Задание
Реализуйте функцию sanitize()
, которая принимает на вход объект и массив ключей. Также она должна возвращать новый объект, но уже без указанных полей.
const user = sanitize({
name: 'John',
password: '1q2w3e',
token: 'test',
}, ['password', 'token']); // { name: string }
console.log(user); // => { name: 'John' }
Обратите внимание, что в выходном типе также не должно быть этих полей.
Полезное
Команда проекта находится в телеграм-сообществе. Там можно задать любой вопрос и повлиять на проект
Если вы зашли в тупик, то самое время поговорить с нашим асситентом Тота во вкладке "ИИ-помощник":
Тесты устроены таким образом, что они проверяют решение разными способами и на разных данных. Часто решение работает с одними входными данными, но не работает с другими. Чтобы разобраться с этим моментом, изучите вкладку «Тесты» и внимательно посмотрите на вывод ошибок, в котором есть подсказки.
Это нормально 🙆, в программировании одну задачу можно выполнить множеством способов. Если ваш код прошел проверку, то он соответствует условиям задачи. В редких случаях бывает, что решение подогнано под тесты, но это видно сразу.
Создавать обучающие материалы, понятные для всех без исключения, довольно сложно. Мы очень стараемся, но всегда есть что улучшать. Если вы встретили материал, который вам непонятен, опишите проблему в обратной связи нашего сообщества
Ваше упражнение проверяется по этим тестам
import { expect, test, expectTypeOf } from 'vitest';
import sanitize from './index';
test('sanitize', () => {
const obj = {
name: 'John',
age: 30,
password: '123456',
};
expect(sanitize(obj, ['name', 'age'])).toEqual({
password: '123456',
});
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const user = sanitize(obj, ['password']);
expectTypeOf(user).toMatchTypeOf<{ name: string; age: number }>();
const params = {
page: 1,
limit: 10,
filter: {
name: 'John',
},
};
const query = sanitize(params, ['filter']);
expect(query).toEqual({
page: 1,
limit: 10,
});
expectTypeOf(query).toMatchTypeOf<{ page: number; limit: number, }>();
});
Решение учителя откроется через:
20:00
