Skip to content

Commit 1932b28

Browse files
authored
Merge pull request #305 from someden/add-35-mapping-modifiers
Add 35-mapping-modifiers
2 parents 70217cc + 3771d03 commit 1932b28

File tree

7 files changed

+255
-0
lines changed

7 files changed

+255
-0
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
test:
2+
@ test.sh
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
---
2+
3+
name: Модификаторы сопоставления типов (Mapping Modifiers)
4+
theory: |
5+
6+
При сопоставлении типов можно менять атрибуты свойств такие как неизменность (immutability) и необязательность (optionality). Делается это с помощью соответствующих модификаторов: `readonly` и `?`.
7+
8+
Чтобы добавить или удалить эти модификаторы, можно использовать префиксы `+` или `-`. Если не использовать префикс, то подразумевается что модификатор будет добавлен, то есть по умолчанию префикс `+`.
9+
10+
Примеры использования модификаторов есть в Utility Types:
11+
12+
```typescript
13+
/**
14+
* Делает все свойства типа `T` необязательными,
15+
* то есть добавляет атрибут `?`.
16+
*/
17+
type Partial<T> = {
18+
[P in keyof T]?: T[P];
19+
};
20+
21+
/**
22+
* Делает все свойства типа `T` обязательными,
23+
* то есть удаляет атрибут `?`.
24+
*/
25+
type Required<T> = {
26+
[P in keyof T]-?: T[P];
27+
};
28+
29+
/**
30+
* Делает все свойства типа `T` неизменяемыми,
31+
* то есть добавляет атрибут `readonly`.
32+
*/
33+
type Readonly<T> = {
34+
readonly [P in keyof T]: T[P];
35+
};
36+
```
37+
38+
Подобным образом можно написать и тип, который делает все свойства типа изменяемыми, то есть удаляет атрибут `readonly`:
39+
40+
```typescript
41+
type Mutable<T> = {
42+
-readonly [P in keyof T]: T[P];
43+
};
44+
```
45+
46+
Благодаря таким типам легче далеть производные типы из уже имеющихся.
47+
48+
Например, в приложении может быть тип `User` для не авторизованного пользователя у которого все поля не обязательные:
49+
50+
```typescript
51+
type User = {
52+
id?: string;
53+
firstName?: string;
54+
secondName?: string;
55+
email?: string;
56+
};
57+
```
58+
59+
Из него можно сделать авторизованного пользователя с помощью типа `Required`:
60+
61+
```typescript
62+
type AuthorizedUser = Required<DefaultUser>;
63+
```
64+
65+
instructions: |
66+
67+
Реализуйте функцию `deepFreeze()`, которая принимает на вход объект и делает его самого, его поля и все вложенные объекты неизменяемыми и возвращает этот объект.
68+
Предполагается что поля объекта и поля вложенных объектов не содержат массивы, только простые типы данных и объекты.
69+
70+
```typescript
71+
const user = deepFreeze({
72+
name: 'John',
73+
password: '1q2w3e',
74+
token: 'test',
75+
});
76+
77+
user.name = 'Alex'; // Error: Cannot assign to 'name' because it is a read-only property.
78+
```
79+
80+
Нужно использовать встроенный в JavaScript метод [Object.freeze()](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze).
81+
82+
tips:
83+
- |
84+
[Официальная документация Mapped Types](https://www.typescriptlang.org/docs/handbook/2/mapped-types.html)
85+
- |
86+
[Официальная документация Mapping Modifiers](https://www.typescriptlang.org/docs/handbook/2/mapped-types.html#mapping-modifiers)
87+
- |
88+
[Официальная документация Partial](https://www.typescriptlang.org/docs/handbook/utility-types.html#partialtype)
89+
- |
90+
[Официальная документация Required](https://www.typescriptlang.org/docs/handbook/utility-types.html#requiredtype)
91+
- |
92+
[Официальная документация Readonly](https://www.typescriptlang.org/docs/handbook/utility-types.html#readonlytype)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// BEGIN
2+
type DeepReadonly<T> = {
3+
readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
4+
};
5+
6+
const deepFreeze = <T extends object>(obj: T): DeepReadonly<T> => {
7+
const freezedObj = Object.freeze(obj);
8+
9+
Object.values(freezedObj).forEach((value) => {
10+
if (typeof value === 'object' && value !== null) {
11+
deepFreeze(value);
12+
}
13+
});
14+
15+
return freezedObj;
16+
};
17+
// END
18+
19+
export default deepFreeze;
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
Реализуйте функцию `deepFreeze()`, которая принимает на вход объект и делает его самого, его поля и все вложенные объекты неизменяемыми и возвращает этот объект.
2+
Предполагается что поля объекта и поля вложенных объектов не содержат массивы, только простые типы данных и объекты.
3+
4+
```typescript
5+
const user = deepFreeze({
6+
name: 'John',
7+
password: '1q2w3e',
8+
token: 'test',
9+
});
10+
11+
user.name = 'Alex'; // Error: Cannot assign to 'name' because it is a read-only property.
12+
```
13+
14+
Нужно использовать встроенный в JavaScript метод [Object.freeze()](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze).
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
При сопоставлении типов можно менять атрибуты свойств такие как неизменность (immutability) и необязательность (optionality). Делается это с помощью соответствующих модификаторов: `readonly` и `?`.
2+
3+
Чтобы добавить или удалить эти модификаторы, можно использовать префиксы `+` или `-`. Если не использовать префикс, то подразумевается что модификатор будет добавлен, то есть по умолчанию префикс `+`.
4+
5+
Примеры использования модификаторов есть в Utility Types:
6+
7+
```typescript
8+
/**
9+
* Делает все свойства типа `T` необязательными,
10+
* то есть добавляет атрибут `?`.
11+
*/
12+
type Partial<T> = {
13+
[P in keyof T]?: T[P];
14+
};
15+
16+
/**
17+
* Делает все свойства типа `T` обязательными,
18+
* то есть удаляет атрибут `?`.
19+
*/
20+
type Required<T> = {
21+
[P in keyof T]-?: T[P];
22+
};
23+
24+
/**
25+
* Делает все свойства типа `T` неизменяемыми,
26+
* то есть добавляет атрибут `readonly`.
27+
*/
28+
type Readonly<T> = {
29+
readonly [P in keyof T]: T[P];
30+
};
31+
```
32+
33+
Подобным образом можно написать и тип, который делает все свойства типа изменяемыми, то есть удаляет атрибут `readonly`:
34+
35+
```typescript
36+
type Mutable<T> = {
37+
-readonly [P in keyof T]: T[P];
38+
};
39+
```
40+
41+
Благодаря таким типам легче далеть производные типы из уже имеющихся.
42+
43+
Например, в приложении может быть тип `User` для не авторизованного пользователя у которого все поля не обязательные:
44+
45+
```typescript
46+
type User = {
47+
id?: string;
48+
firstName?: string;
49+
secondName?: string;
50+
email?: string;
51+
};
52+
```
53+
54+
Из него можно сделать авторизованного пользователя с помощью типа `Required`:
55+
56+
```typescript
57+
type AuthorizedUser = Required<DefaultUser>;
58+
```
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
name: Модификаторы сопоставления типов (Mapping Modifiers)
2+
tips:
3+
- >
4+
[Официальная документация Mapped
5+
Types](https://www.typescriptlang.org/docs/handbook/2/mapped-types.html)
6+
- >
7+
[Официальная документация Mapping
8+
Modifiers](https://www.typescriptlang.org/docs/handbook/2/mapped-types.html#mapping-modifiers)
9+
- >
10+
[Официальная документация
11+
Partial](https://www.typescriptlang.org/docs/handbook/utility-types.html#partialtype)
12+
- >
13+
[Официальная документация
14+
Required](https://www.typescriptlang.org/docs/handbook/utility-types.html#requiredtype)
15+
- >
16+
[Официальная документация
17+
Readonly](https://www.typescriptlang.org/docs/handbook/utility-types.html#readonlytype)
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import * as ta from 'type-assertions';
2+
3+
import deepFreeze from './index';
4+
5+
test('deepFreeze', () => {
6+
const obj = {
7+
name: 'John',
8+
age: 30,
9+
location: {
10+
city: 'York',
11+
coordinates: {
12+
lat: 53.958,
13+
lon: -1.093,
14+
},
15+
},
16+
};
17+
18+
const user = deepFreeze(obj);
19+
20+
expect(user).toEqual({
21+
name: 'John',
22+
age: 30,
23+
location: {
24+
city: 'York',
25+
coordinates: {
26+
lat: 53.958,
27+
lon: -1.093,
28+
},
29+
},
30+
});
31+
32+
expect(() => {
33+
// @ts-expect-error Cannot assign read-only property.
34+
user.age = 20;
35+
}).toThrow();
36+
37+
expect(() => {
38+
// @ts-expect-error Cannot assign nested read-only property.
39+
user.location.city = 'London';
40+
}).toThrow();
41+
42+
ta.assert<ta.Equal<typeof user, Readonly<{
43+
name: string,
44+
age: number,
45+
location: Readonly<{
46+
city: string,
47+
coordinates: Readonly<{
48+
lat: number,
49+
lon: number,
50+
}>,
51+
}>,
52+
}>>>();
53+
});

0 commit comments

Comments
 (0)