일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- 이펙티브타입스크립트
- 테오의 스프린트
- framer
- 개발자를 위한 글쓰기 가이드
- 알고리즘
- Semantic Versioning
- 코드트리
- 타입스크립트
- JUNCTION2023
- useState
- React-Router-Dom
- VS Code
- 글또 10기
- TS
- Framer motion
- CSS방법론
- 캐나다취준
- SemVer
- CSS
- 개발자 원칙
- typescript
- 글또
- react
- ASP.NET
- 시스템디자인
- 캐나다개발자
- Effective Typescript
- framer-motion
- JSBridge
- 회고
- Today
- Total
큰 꿈은 파편이 크다!!⚡️
이펙티브 타입스크립트 🦅 2장 (3) 본문
15. 동적 데이터에 인덱스 시그니처 사용하기
JS 객체는 문자열 키를 타입의 값에 관계없이 매핑한다. TS에서는 타입에 인덱스 시그니처를 명시하여 매핑을 표현할 수 있다.
type Rocket = {[property: string]: string};
const rocket: Rocket = {
name: 'Falcon 9',
variant: 'v1.0',
thrust: '4,940 kN',
}; // OK
인덱스 시그니처 [property: string]: string
의 의미
- 키의 이름: 키의 위치만 표시하는 용도
- 키의 타입: string | number | symbol이어야하지만 보통 string 사용
- 값의 타입: 무엇이든
단점도 있다.
- 잘못된 키를 포함한 모든 키를 허용하게 되거나, 특정 키가 필요없을 수 있다
- 키마다 다른 타입을 가질 수 없다
- 키는 뭐든지 가능하므로 자동 완성 기능이 동작하지 않는다
결국, 위와 같은 예제에서 사용하는 인덱스 시그니처는 부정확한 반면, 동적 데이터를 표현할 때에는 적합하다. 예를 들어 CSV파일의 데이터 행을 열 이름과 값으로 매핑하고 싶다고 할 때, 열 이름을 미리 알 수 없으므로 인덱스 시그니처를 사용할 수 있다.
function parseCSV(input: string): {[columnName: string]: string}[] {
const lines = input.split('\n');
const [header, ...rows] = lines;
return rows.map(rowStr => {
const row: {[columnName: string]: string} = {};
rowStr.split(',').forEach((cell, i) => {
row[header[i]] = cell;
});
return row;
});
}
필드가 제한된 경우라면 가능한 정확한 타입을 사용하고, 런타임 때까지 객체의 속성을 알 수 없는 경우에만 인덱스 시그니처를 사용하자.
16. number 인덱스 시그니처보다는 Array, 튜플, ArrayLike를 사용하기
JS에서 객체를 구성하는 키는 문자열(ECMA2015 이후 심벌 포함)로 변환되어 저장되거나, 값에 접근할 수 있다. 숫자는 키가 될 수 없으며 속성 이름으로 숫자를 사용하면 JS런타임은 문자열로 변환한다. 객체의 키를 나열해보면 키가 문자열로 출력된다.
> { 1: 2, 3: 4 }
{ '1': 2, '3': 4 }
> Object.keys(x)
[ '1', '3' ]
크롬의 콘솔에서 확인해보면 마치 키가 숫자로 나오는 것 같아 의문이 생겼지만 MDN에서 문자열 혹은 심벌로 변환되는 것을 확인했다. 콘솔에서도 key의 타입을 확인하면 string으로 나타난다.
TS에서는 숫자 키를 허용하고, 문자열 키와 다른 것으로 인식한다.
런타임에서는 ECMAScript 표준에 따라 문자열 키로 인식하므로 아래와 같이 숫자 타입의 키를 활용하는 것은 가상이라고 볼 수 있지만, 타입 체크 시점에 오류를 잡을 수 있게 해주는 TS의 장점으로 활용할 수 있다.
interface Array<T> {
//...
[n: nummber]: T;
}
const xs = [1, 2, 3];
const x0 = xs[0]; // OK
const x1 = xs['1'];
// ~~~ Element implicitly has an 'any' type
// because index expression is not of type 'number'
인덱스 시그니처가 number로 표현되어 있다면 입력한 값이 number임을 의미하지만 실제 런타임에 사용되는 키는 string 타입이다. 일반적으로 number을 타입의 인덱스 시그니처로 사용할 일은 적다. 숫자 속성이 특별한 의미를 지닌다는 오해를 일으킬 수 있기 때문이다. 만약 숫자를 사용하여 인덱스 항목을 지정한다면 Array 또는 튜플 타입을 사용하게 될 것이다.
아래와 같은 방식은 배열을 순회하기에 좋은 방법이 아니다. 인덱스가 필요없다면 for-of
를 사용하고, 인덱스의 타입이 중요하다면 Array.prototype.forEach
를 사용하자. 루프가 중간에 멈춰야 한다면 C 스타일의 for(;;)
을 사용하자.
const xs = [1, 2, 3];
//👎
const keys = Object.keys(xs); // Type is string[]
for (const key in xs) {
key; // Type is string
const x = xs[key]; // Type is number
}
어떤 길이를 가지는 배열과 비슷한 형태의 튜플을 사용하고 싶다면 TS의 ArrayLike
타입을 사용하자.
const xs = [1, 2, 3];
function checkedAccess<T>(xs: ArrayLike<T>, i: number): T {
if (i < xs.length) {
return xs[i];
}
throw new Error(`Attempt to access ${i} which is past end of array.`)
}
17. 변경 관련 오류 방지를 위해 readyonly 사용하기
배열의 모든 값의 합을 반환하는 arraySum
함수를 아래와 같이 구현한다면 함수 호출 후 원래의 배열이 모두 비게 된다.
function arraySum(arr: number[]) {
let sum = 0, num;
while ((num = arr.pop()) !== undefined) {
sum += num;
}
return sum;
}
이를 방지하기 위해 readonly
접근 제어자를 사용하면 원래의 배열을 변경하지 않는다는 선언을 할 수 있다. 단, 배열을 변경할 수 없기 때문에 pop
을 비롯하여 배열을 변경하는 메서드를 호출할 수 없어 오류가 발생한다.
function arraySum(arr: readonly number[]) {
let sum = 0, num;
while ((num = arr.pop()) !== undefined) {
// ~~~ 'pop' does not exist on type 'readonly number[]'
sum += num;
}
return sum;
}
JS와 TS에서는 함수가 매개변수를 변경하지 않는다고 가정하기 때문에 readonly
로 변경 여부를 명시하는 것이 좋다.
따라서, 위에서 오류가 발생한 코드를 아래와 같이 배열을 변경하지 않는 방식으로 바꿀 수 있다.
function arraySum(arr: readonly number[]) {
let sum = 0;
for (const num of arr) {
sum += num;
}
return sum;
}
18. 매핑된 타입을 사용하여 값을 동기화하기
산점도(Scatter plot)을 그리는 UI 컴포넌트의 인터페이스를 아래와 같이 작성할 수 있다. 타입 체커를 사용하여 데이터나 디스플레이 속성이 변경될 때만 차트를 다시 그리고, 이벤트 핸들러가 변경되었을때는 다시 그리지 않도록 최적화하는 방법을 생각해 보자.
interface ScatterProps {
// The data
xs: number[];
ys: number[];
// Display
xRange: [number, number];
yRange: [number, number];
color: string;
// Events
onClick: (x: number, y: number, index: number) => void;
}
책에서 제시하는 방법은 REQUIRES_UPDATE
를 사용해서 화면 갱신의 필요성을 확인하고, REQUIRES_UPDATE
내부에서는 속성을 ScatterProps
에 국한시켜 속성 이름을 변경하거나 새로운 속성을 추가할 때 오류를 발생시킨다.
const REQUIRES_UPDATE: {[k in keyof ScatterProps]: boolean} = {
xs: true,
ys: true,
xRange: true,
yRange: true,
color: true,
onClick: false,
};
function shouldUpdate(
oldProps: ScatterProps,
newProps: ScatterProps
) {
let k: keyof ScatterProps;
for (k in oldProps) {
if (oldProps[k] !== newProps[k] && REQUIRES_UPDATE[k]) {
return true;
}
}
return false;
}
마무리
꾸역꾸역 2장을 마무리했다. 세번까지 읽어도 확실하게 이해했다고 말하기가 어려운 내용이었다.
코드를 읽고 그대로 사용하기는 쉽겠지만 “왜” 이렇게 사용하는지에 대한 연결이 아직은 미숙한 것 같다. 그리고 몇 번을 읽어도 낯선 용어들이 있어서 그런지 학습의 필요성을 더욱 느낀다 ㅎㅎ
'Web FE' 카테고리의 다른 글
리액트 성능 향상 첫걸음: useMemo, useCallback, React.memo (0) | 2023.03.12 |
---|---|
리액트 앱 배포 후 Cache 문제 🧹 삽질기 (0) | 2023.02.26 |
이펙티브 타입스크립트 🦅 2장 (2) (0) | 2022.09.28 |
이펙티브 타입스크립트 🦅 2장 (1) (0) | 2022.08.28 |
이펙티브 타입스크립트 🦅 1장 (0) | 2022.08.15 |