Чтобы при работе с объектными типами избежать дублирования полей и переиспользовать существующие, мы можем использовать механизм поиска типов — 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' }
Обратите внимание, что в выходном типе также не должно быть этих полей.
Если вы зашли в тупик, то самое время задать вопрос в «Обсуждениях». Как правильно задать вопрос:
Тесты устроены таким образом, что они проверяют решение разными способами и на разных данных. Часто решение работает с одними входными данными, но не работает с другими. Чтобы разобраться с этим моментом, изучите вкладку «Тесты» и внимательно посмотрите на вывод ошибок, в котором есть подсказки.
Это нормально 🙆, в программировании одну задачу можно выполнить множеством способов. Если ваш код прошел проверку, то он соответствует условиям задачи.
В редких случаях бывает, что решение подогнано под тесты, но это видно сразу.
Создавать обучающие материалы, понятные для всех без исключения, довольно сложно. Мы очень стараемся, но всегда есть что улучшать. Если вы встретили материал, который вам непонятен, опишите проблему в «Обсуждениях». Идеально, если вы сформулируете непонятные моменты в виде вопросов. Обычно нам нужно несколько дней для внесения правок.
Кстати, вы тоже можете участвовать в улучшении курсов: внизу есть ссылка на исходный код уроков, который можно править прямо из браузера.
Ваше упражнение проверяется по этим тестам
1import * as ta from 'type-assertions';
2
3import sanitize from './index';
4
5test('sanitize', () => {
6 const obj = {
7 name: 'John',
8 age: 30,
9 password: '123456',
10 };
11
12 expect(sanitize(obj, ['name', 'age'])).toEqual({
13 password: '123456',
14 });
15
16 const user = sanitize(obj, ['password']);
17
18 ta.assert<ta.Equal<typeof user, { name: string; age: number }>>();
19
20 const params = {
21 page: 1,
22 limit: 10,
23 filter: {
24 name: 'John',
25 },
26 };
27
28 const query = sanitize(params, ['filter']);
29 expect(query).toEqual({
30 page: 1,
31 limit: 10,
32 });
33
34 ta.assert<ta.Equal<typeof query, { page: number; limit: number, }>>();
35});
36
Решение учителя откроется через: