Skip to content

Commit 56df915

Browse files
committed
docs: Add docs about item15-18
1 parent 10eb35b commit 56df915

1 file changed

Lines changed: 190 additions & 0 deletions

File tree

chapter2/lado/item15-18.md

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
## 아이템 15. 동적 데이터에 인덱스 시그니처 사용하기
2+
3+
- 인덱스 시그니처
4+
- 동적 데이터를 표현할 때 사용
5+
```tsx
6+
type Rocket = { [property: string]: string }
7+
const rocket: Rocket = {
8+
name: 'Falcon 9',
9+
variant: 'v1.0',
10+
thrust: '4,940 kN',
11+
} // 정상
12+
```
13+
- 키의 이름 `property`
14+
- 키의 위치만 표시하는 용도. 타입 체커에서는 사용하지 않음.
15+
- 키의 타입 `string`
16+
- string이나 number 또는 symbol의 조합이어야 하지만, 보통은 string을 사용.
17+
- 값의 타입 `string`
18+
- 어떤 것이든 될 수 있음.
19+
- 인덱스 시그니처의 4가지 단점
20+
- 잘못된 키를 포함해 모든 키를 허용한다. 오타를 잡을 수 없다.
21+
- 객체 안에 프로퍼티가 하나도 없는 {}도 유효한 타입이 된다.
22+
- 키마다 다른 타입을 가질 수 없고, 키들의 타입이 통일되어야 한다.
23+
- 키를 입력할 때 키에는 어떤 값도 들어올 수 있으므로 자동 완성 기능이 동작하지 않는다.
24+
- 인덱스 시그니처 대신 사용 가능한 2가지 대안
25+
- `Record<K, T>`
26+
- 키 타입에 유연성을 제공하는 제너릭 타입
27+
```tsx
28+
type Vec3D = Record<'x' | 'y' | 'z', number>
29+
// type Vec3D = Record<키에 들어올 수 있는 타입, 값의 타입>;
30+
// Type Vec3D = {
31+
// x: number;
32+
// y: number;
33+
// z: number;
34+
// }
35+
```
36+
- 인덱스 시그니처의 파라미터 타입은 리터럴이 될 수 없다.
37+
```tsx
38+
type fruitInfo = {
39+
[fruit: '사과' | '딸기' | '포도']: number
40+
}
41+
// An index signature parameter type cannot be a literal type or generic type. Consider using a mapped object type instead.
42+
```
43+
- 매핑된 타입 사용
44+
- 키마다 별도의 타입을 사용하게 해준다.
45+
```tsx
46+
type ABC = { [k in 'a' | 'b' | 'c']: k extends 'b' ? string : number }
47+
// Type ABC = {
48+
// a: number;
49+
// b: string;
50+
// c: number;
51+
// }
52+
```
53+
- 런타임 때까지 객체의 속성을 알 수 없을 경우에만(예를 들어 CSV 파일에서 로드하는 경우) 인덱스 시그니처를 사용하도록 한다.
54+
- 안전한 접근을 위해 인덱스 시그니처의 값 타입에 undefined를 추가하는 것을 고려해야 한다.
55+
- 어떤 타입에 가능한 필드가 제한되어 있는 경우라면 인덱스 시그니처로 모델링하지 말아야 한다.
56+
```tsx
57+
interface Row1 {
58+
[column: string]: number
59+
} // 너무 광범위
60+
interface Row2 {
61+
a: number
62+
b?: number
63+
c?: number
64+
d?: number
65+
} // 최선
66+
type Row3 =
67+
| { a: number }
68+
| { a: number; b: number }
69+
| { a: number; b: number; c: number }
70+
| { a: number; b: number; c: number; d: number } // 가장 정확하지만 사용하기 번거로움
71+
```
72+
- 연관 배열(associative array)의 경우, 객체에 인덱스 시그니처를 사용하는 대신 Map 타입을 사용하는 것을 고려할 수 있다. ← 잘 이해는 안되지만 298페이지에 관련 내용 나온다고 함.
73+
74+
<br />
75+
76+
## 아이템 16. number 인덱스 시그니처보다는 Array, 튜플, ArrayLike를 사용하기
77+
78+
- 자바스크립트의 이상한 동작(feat. 암시적 타입 강제)
79+
- 배열은 객체이므로 키는 숫자가 아니라 문자열이다.
80+
- 자바스크립트에서는 숫자나 복잡한 객체를 키로 사용하려고 하면 문자열로 변환해 버린다.
81+
- 배열에서 특정 요소를 접근할 때 인덱스를 문자열로 적어도 가능. 배열의 키를 Object.keys로 나열해보면 문자열로 출력된다.
82+
- 타입스크립트는 암시적 타입 강제의 혼란을 바로잡기 위해 숫자 키를 허용하고, 문자열 키와 다른 것으로 인식한다. (런타입에서는 문자열 키로 인식)
83+
- 인덱스를 신경 쓰지 않는다면, 배열을 순회할 때 `for...in` 보다 `for...of` 를 써라. 인덱스가 중요하다면 `forEach` 를 써라.
84+
85+
```tsx
86+
const xs = [1, 2, 3]
87+
88+
const keys = Object.keys(xs) // 타입이 stirng[]
89+
for (const key in xs) {
90+
key // 타입이 string
91+
const x = xs[key] // 타입이 number
92+
}
93+
94+
for (const x of xs) {
95+
x // 타입이 number
96+
}
97+
98+
xs.forEach((x, i) => {
99+
i // 타입이 number
100+
x // 타입이 number
101+
})
102+
```
103+
104+
- `for...in``for...of``for` 루프보다 몇 배나 느리다.
105+
106+
- 인덱스 시그니처에 number를 사용하기 보다 Array, 튜플, ArrayLike 타입을 사용하는 것이 좋다.
107+
- 어떤 길이를 가지는 배열과 비슷한 형태의 튜플을 사용하고 싶다면 타입스크립트에 있는 ArrayLike 타입을 사용한다.
108+
```tsx
109+
const tupleLike: ArrayLike<string> = {
110+
'0': 'A',
111+
'1': 'B',
112+
length: 2,
113+
}
114+
```
115+
116+
<br />
117+
118+
## 아이템 17. 변경 관련된 오류 방지를 위해 readonly 사용하기
119+
120+
- `readonly number[]``number[]` 가 구분되는 특징
121+
- 배열의 요소를 읽을 수 있지만, 쓸 수는 없다.
122+
- length를 읽을 수 있지만, 바꿀 수는 없다.
123+
- 배열을 변경하는 pop을 비롯한 다른 메서드를 호출할 수 없다.
124+
- `number[]``readonly number[]` 보다 기능이 많기 때문에 `readonly number[]` 의 서브 타입이 된다.
125+
- 매개변수를 readonly로 선언하면 아래와 같은 일이 생긴다.
126+
- 타입스크립트는 매개변수가 함수 내에서 변경이 일어나는지 체크한다.
127+
- 호출하는 쪽에서는 함수가 매개변수를 변경하지 않는다는 보장을 받게 된다.
128+
- 호출하는 쪽에서 함수에 readonly 배열을 매개변수로 넣을 수도 있다.
129+
- 매개변수를 readonly로 선언할 때의 장점
130+
- 더 넓은 타입으로 호출할 수 있고, 의도치 않은 변경은 방지될 것이다.
131+
- 지역 변수와 관련된 모든 종류의 변경 오류를 방지할 수 있고, 변경이 발생하는 코드도 쉽게 찾을 수 있다.
132+
- 함수가 매개변수를 수정하지 않는다면 readonly로 선언하는 것이 좋다. readonly 매개변수는 인터페이스를 명확하게 하며, 매개변수가 변경되는 것을 방지한다.
133+
- 단점?
134+
- 어떤 함수를 readonly로 만들면 그 함수를 호출하는 다른 함수도 모두 readonly로 만들어야 한다. 다른 라이브러리에 있는 함수를 호출하는 경우라면 타입 선언을 바꿀 수 없기 때문에 타입 단언문을 사용해야 한다.
135+
- readonly, Readonly 제너릭은 얕게 동작한다는 것에 유의하며 사용해야 한다.
136+
137+
```tsx
138+
interface Outer {
139+
inner: {
140+
x: number
141+
}
142+
}
143+
144+
const o: Readonly<Outer> = { inner: { x: 0 } }
145+
o.inner = { x: 1 }
146+
// Cannot assign to 'inner' because it is a read-only property.
147+
o.inner.x = 1
148+
149+
type T = Readonly<Outer>
150+
// type T = {
151+
// readonly inner: {
152+
// x: number;
153+
// };
154+
// }
155+
```
156+
157+
- 깊은 readonly 타입을 쓰고 싶으면 ts-essentials에 있는 DeepReadonly 제너릭을 사용하면 된다.
158+
- 인덱스 시그니처에도 readonly를 쓸 수 있다. 일기는 허용하되 쓰기를 방지하는 효과가 있다.
159+
```tsx
160+
let obj: { readonly [k: string]: number } = {}
161+
// 또는 Readonly<{[k: string]: number}>
162+
obj.hi = 45
163+
// Index signature in type '{ readonly [k: string]: number; }' only permits reading.
164+
obj = { ...obj, hi: 12 }
165+
```
166+
167+
<br />
168+
169+
## 아이템 18. 매핑된 타입을 사용하여 값을 동기화하기
170+
171+
- 어떤 인터페이스에 속성이 추가될 때마다 다른 인터페이스의 속성도 같이 추가되어야 한다고 할 때 매핑된 타입을 사용해 값을 동기화하는 방식을 쓰는 것이 좋다.
172+
173+
```tsx
174+
interface ScatterProps {
175+
xs: number[]
176+
ys: number[]
177+
color: string
178+
onClick: () => void
179+
onDoubleClick: () => void
180+
}
181+
182+
const REQUIRES_UPDATE: { [k in keyof ScatterProps]: boolean } = {
183+
xs: true,
184+
ys: true,
185+
color: true,
186+
onClick: false,
187+
}
188+
// Property 'onDoubleClick' is missing in type '{ xs: true; ys: true; color: true; onClick: false; }' but required in type '{ xs: boolean; ys: boolean; color: boolean; onClick: boolean; onDoubleClick: boolean; }'.
189+
// onDoubleClick 속성을 추가하라고 에러가 뜬다.
190+
```

0 commit comments

Comments
 (0)