대상 : JS나 TS를 안써본 초급자가 아니라, 이미 사용해봐서 전문가가 되고자 하는 초급자나 중급자
Untitled
추가 공부할 것
- 잉여 속성 체크
- 객체 리터럴
- 왜 const o: Options1 = { darkmode: true, title: 'Ski Free' };는 객체 리터럴이고, const intermediate = { darkmode: true, title: 'Ski Free' };이건 객체 리터럴이 아닌가?
- 선언 병합
- 타입과 인터페이스
- 링크
전체 내용 요약
1. Typescript는 Javascript의 상위집합이다.
모든 Javascript는 Typescript이다. 하지만, 모든 Typescript는 Javascript가 아니다. 왜냐하면 Typescript만의 문법이 존재하기 때문이다.
2. Typescript는 타입체커이다.
const city = 'seoul';
console.log(city.toUpperCase());
위 코드가 Javascript에서는 실행을 해야 에러를 발생시킨다. 하지만, Typescript는 ‘정적’ 타입 시스템이기에 에러를 내뱉는다.
3. Typescript는 Javascript 런타임을 ‘모델링’한다.
const x = 2 + '3';
다른 언어였다면 런타임 오류를 내뱉었을 것이다. 하지만, Typescript는 Javascript런타임을 모델링하기에, 에러를 발생시키지 않는다.
4. tsconfig.json
-
noImplicity: 모든 any 타입을 허용하지 않음const add(a,b:number){ // 'a'매개변수에는 암시적으로 'any'형식이 포함됩니다.}- JS → TS로 마이그레이션하는 경우가 아닌 이상 True 권장
-
strictNullChecks: null과 undefined를 모든 타입에서 허용하지 않음const x:number = null; // 'null' 형식은 'number'형식에 할당할 수 없습니다.- TS가 처음이 아니라면 True 권장
5. 코드 생성과 타입은 관계가 없다.
컴파일은 타입 체크와 독립적으로 동작하기에, 타입 오류가 있는 코드도 컴파일이 가능하다.
만약 타입 에러가 있을 때 컴파일을 하지 않으려면 noEmitOnError를 추가하면 된다.
6. 런타임에는 타입 체크가 불가능하다.
TS > JS로 컴파일되는 과정에서 TS의 타입은 ‘제거’된다 : interface, type, type구문 모두 제거된다.
// index.ts
interface Square {
width: number;
}
interface Rectangle extends Square {
height: number;
}
type Shape = Square | Rectangle;
function calculateArea(shape: Shape) {
if (shape instanceof Rectangle) {
// ~~~~~~~~~ 'Rectangle' only refers to a type,
// but is being used as a value here
return shape.width * shape.height;
// ~~~~~~ Property 'height' does not exist
// on type 'Shape'
} else {
return shape.width * shape.width;
}
}
에러 : 'Rectangle' only refers to a type, but is being used as a value here
위 코드에서, 컴파일 과정에서 Shape와 Rectanglel의 interface와 type은 제거가 된다. instanaceof 체크는 런타임에 일어나는 JS코드이다. 그러나 Rectangle은 타입이기에 런타임 시점에 아무런 역할을 할 수 없다.
컴파일 후 코드는 다음과 같다.
function calculateArea(shape) {
if (shape instanceof Rectangle) {
return shape.width * shape.height;
}
else {
return shape.width * shape.width;
}
}
해결 방법 1 : 속성 체크
function calculateArea(shape: Shape) {
if ('height' in shape) {
shape; // Type is Rectangle
return shape.width * shape.height;
} else {
shape; // Type is Square
return shape.width * shape.width;
}
}
height 속성이 shape에 있는 지 존재 여부를 런타임에서 확인하여 Rectangle인지 체크한다. 이 때, 타입 체커도 Shape의 타입을 Rectangle로 보정해주기에 오류가 사라진다.
해결 방법 2 : 태그 기법
interface Square {
kind: 'square';
width: number;
}
interface Rectangle {
kind: 'rectangle';
height: number;
width: number;
}
type Shape = Square | Rectangle;
function calculateArea(shape: Shape) {
if (shape.kind === 'rectangle') {
shape; // Type is Rectangle
return shape.width * shape.height;
} else {
shape; // Type is Square
return shape.width * shape.width;
}
}
Square와 Rectangle에 kind 속성을 추가하여, 이 속성으로 Rectangle인지 타입 체크를 한다.
해결 방법 3 : class 사용
class Square {
constructor(public width: number) {}
}
class Rectangle extends Square {
constructor(public width: number, public height: number) {
super(width);
}
}
type Shape = Square | Rectangle;
function calculateArea(shape: Shape) {
if (shape instanceof Rectangle) {
shape; // Type is Rectangle
return shape.width * shape.height;
} else {
shape; // Type is Square
return shape.width * shape.width; // OK
}
}
class로 선언하면 타입과 값으로 모두 사용할 수 있으므로 오류가 발생하지 않는다.
type shape = Square | Rectangle에서는 type으로 사용되지만, shape isntaceof Rectangle에서는 값으로 사용된다.
Typescript 타입은 런타임 성능에 영향을 주지 않는다.
타입스크립트는 ‘런타임’ 오버헤드가 없는 대신, ‘빌드타임’오버헤드가 있다. 오버헤드가 커지면, 빌드 도구에서 트랜스파일만을 설정하여 타입 체크를 건너뛸 수 있다.
구조적 타이핑에 익숙해지기
구조적 타이핑 : 오직 멤버만으로 타입을 관계시키는 방식
- y가 최소한 x와 동일한 멤버를 가지고 있다면 x와 y는 호환된다.
↔ 명목적 타이핑
interface Vector2D {
x: number;
y: number;
}
function calculateLength(v: Vector2D) {
return Math.sqrt(v.x * v.x + v.y * v.y);
}
interface NamedVector {
name: string;
x: number;
y: number;
}
interface Vector3D {
x: number;
y: number;
z: number;
}
function normalize(v: Vector3D) {
const length = calculateLength(v);
return {
x: v.x / length,
y: v.y / length,
z: v.z / length,
};
}
여기서, Typescript의 구조적 타이핑 속성으로 인해 타입 에러는 발생하지 않는다. x,y,z는 Vector2D의 프로퍼티인 x,y를 모두 포함하기 때문이다.
function calculateLengthL1(v: Vector3D) {
let length = 0;
for (const axis of Object.keys(v)) {
const coord = v[axis];
// ~~~~~~~ Element implicitly has an 'any' type because ...
// 'string' can't be used to index type 'Vector3D'
length += Math.abs(coord);
}
return length;
}
axis는 v의 key값만을 순회하는 값임에도 타입 에러가 발생한다. 왜냐하면, axis는 Typescript의 구조적 타이핑으로 인해 다른 값일 수 있기 때문이다.
e.g. Vector3D : x,y,z인데, v에 x,y,z에 추가로 address가 들어가도 이는 Vector3D타입이라 할 수 있기 때문이다.
const vec3D = {x: 3, y: 4, z: 1, address: '123 Broadway'};
calculateLengthL1(vec3D); // OK, returns NaN
이런 경우, 루프보다는 모든 속성을 각각 더하는 구현이 더 낫다.
function calculateLengthL1(v: Vector3D) {
return Math.abs(v.x) + Math.abs(v.y) + Math.abs(v.z);
}
- 구조적 타이핑의 장점
- 테스트에 용이하다.
- 테스트 코드에서 실제 환경의 DB에 대한 정보가 불필요하다.
- 테스트에 용이하다.
- 라이브러리 간의 의존성을 완벽히 분리할 수 있다.
Any타입 지양하기
- any 타입에는 타입 안정성이 없다.
- any타입에 string타입을, number타입을 입력해도 문제가 없음
- any는 함수 시그니처를 무시해버린다.
- any타입에는 언어 서비스가 적용되지 않는다.
- Tip : 바꾸고 싶은 프로퍼티 드래그 → 우클릭 → Rename Symbol클릭해서 프로퍼티명 변경하면 일괄 수정됨
편집기를 사용하여 타입 시스템 탐색하기
- 마우스를 가져다 대면 타입을 알려줌
- 편집기를 사용하면 어떻게 타입 시스템이 동작하는지, 타입스크립트가 어덯게 타입을 추론하는지 개념을 잡을 수 있음
타입이 값들의 집합이라고 생각하기
- 유닛 타입(=리터럴 타입) :
type A = ‘A’; - 유니온 타입 :
A | B- 타입의 합집합
- 인터섹션 타입 :
A & B- 타입의 교집합
keyof (A&B) = (keyof A) | (keyof B)A extends B와 동일. A가 interface여야함
keyof (A|B) = (keyof A) & (keyof B)- 타입스크립트 용엉와 집합 용어
- never : 공집합 (모든 타입의 부분집합)
- 리터럴 타입 : 원소가 1개인 집합
- type a = ‘A’;
- 값이 T에 할당 가능 : 값이 T의 원소. 값이 T의 부분 집합
- T1이 T2에 할당 가능 : T1이 T2의 부분 집합
- T1이 T2를 상속 : T1이 T2의 부분 집합
- T1 | T2 : T1과 T2의 합집합
- T1 & T2 : T1과 T2의 교집합
- unkown : 전체 집합
- 타입을 값의 집합으로 생각하면 이해하기 편하다.
- 이 집합은 유한(boolean, 리터럴 타입)하거나 무한(number, string)하다.
- 타입스크립트 타입은 엄격한 상속 관계가 아니라 겹쳐지는 집합(벤 다이어그램)으로 표현된다.
- A&B는 A와 B의 속성을 모두 가짐을 의미한다.
interface Point {
x: number;
y: number;
}
type PointKeys = keyof Point; // Type is "x" | "y"
function sortBy<K extends keyof T, T>(vals: T[], key: K): T[] {
// COMPRESS
vals.sort((a, b) => a[key] === b[key] ? 0 : a[key] < b[key] ? -1 : +1);
return vals;
// END
}
const pts: Point[] = [{x: 1, y: 1}, {x: 2, y: 0}];
sortBy(pts, 'x'); // OK, 'x' extends 'x'|'y' (aka keyof T)
// 여기서 T는 Point. 따라서 K extends keyof T는 K extends 'x'|'y'가 된다.
sortBy(pts, 'y'); // OK, 'y' extends 'x'|'y'
sortBy(pts, Math.random() < 0.5 ? 'x' : 'y'); // OK, 'x'|'y' extends 'x'|'y'
sortBy(pts, 'z');
// ~~~ Type '"z"' is not assignable to parameter of type '"x" | "y"
타입 공간과 값 공간의 심벌 구분하기
- 타입스크립트 코드를 읽을 때 타입인지 값인지 구분하는 방법을 터득해야 한다
- Typescript Playground를 활용해 개념을 잡기
interface Person {
first: string;
last: string;
}
function email({
person: Person,
// ~~~~~~ Binding element 'Person' implicitly has an 'any' type
subject: string,
// ~~~~~~ Duplicate identifier 'string'
// Binding element 'string' implicitly has an 'any' type
body: string}
// ~~~~~~ Duplicate identifier 'string'
// Binding element 'string' implicitly has an 'any' type
) { /* ... */ }
위 코드에서 person: Person을 Javascript의 값의 관점에서 해석되었기에 에러가 발생한다. 아래와 같이 작성해주어야 한다.
function email(
{person, subject, body}: {person: Person, subject: string, body: string}
) {
// ...
}
-
모든 값은 타입을 가지지만, 타입은 값을 가지지 않는다.
-
class, enum같은 키워드는 타입과 값 두가지로 사용될 수 있다.
class Cylinder { radius=1; height=1; } function calculateVolume(shape: unknown) { if (shape instanceof Cylinder) { shape // OK, type is Cylinder shape.radius // OK, type is number } } const v = typeof Cylinder; // Value is "function" type T = typeof Cylinder; // Type is typeof Cylinder type PersonEl = Person['first' | 'last']; // Type is string type Tuple = [string, number, Date]; type TupleEl = Tuple[number]; // Type is string | number | Date위 코드에서 const/let/var로 선언한 변수에서 Cylinder class는 값으로 쓰였고, type으로 선얺나 변수에서는 타입으로 쓰였다.
TypleEl이 string|number|Date이 되는 이유는,Tuple[number]는 Tuple[0|1|2]가 되고,Tuple[0|1|2]는 Tuple[0] | Tuple[1] | Tuple[2]가 되기 때문이다. -
typeof, this 그리고 많은 다른 연산자들과 키워드는 타입 공간과 값 공간에서 다른 목적으로 사용될 수 있다.
9. 타입 단언보다는 타입 선언 사용하기
interface Person { name: string };
const people = ['alice', 'bob', 'jan'].map(
(name): Person => ({name})
); // Type is Person[]
(name):Person: name의 타입이 없고, 반환 타입이 Person임을 명시(name:Person): name의 타입이 Person임을 명시하고 반환타입이 없기에 오류 발생- 타업 단언이 필요한 경우 : TS보다 타입 정보를 더 잘 알고 있는 상황
-
DOM 엘리먼트에 접근할 때
document.querySelector('#myButton').addEventListener('click', e => { e.currentTarget // Type is EventTarget const button = e.currentTarget as HTMLButtonElement; button // Type is HTMLButtonElement });
-
- 특벼한 문법(!)을 사용해서 null이 아님을 단언하는 경우도 있음
const elNull = document.getElementById('foo'); // Type is HTMLElement | null
const el = document.getElementById('foo')!; // Type is HTMLElement
- null값이 아님을 확실히 알고 있을 때 사용
-
unknow타입을 사용한 타입 단언
interface Person { name: string; } const el = document.body as unknown as Person; // OK모든 타입은 unknown의 서브타입이기에 unknown이 포함되 단언문은 항상 동작
10. 객체 래퍼 타입 피하기
Javascraipt에는 primitive 타입 7가지가 있다. 그리고 그에 매칭되는 Wrapper 객체가 있다.
- string - String
- number - Number
- boolean - Boolean
- null
- undefined
- symbol - Symbol
- bigint - BigInt
우리가 위 primitive 타입 값에 메서드를 사용할 수 있는 이유는, Javascript 내부적으로 기본형과 객체 타입을 서로 자유롭게 변환하기 때문이다. Javascript는 기본형 > Wrapper객체로 Wrapping > 메서드 호출 > Wrapping한 객체를 버린다.
그래서, 아래와 같은 코드가 가능하다.
const x = "hello";f
x.a = "yeah";.x.a // undefined
11. 잉여 속성 체크의 한계 인지하기
객체 리터럴을 변수에 할당하거나 함수에 매개변수로 전달할 때 잉여 속성 체크가 수행된다. 잉여 속성 체크는 오류를 찾는 효과적인 방법이지만, TS 타입 체커가 수행하는 일반적인 구조적 할당 가능성 체크와 역할이 다르다. 할당의 개념을 정확히 알아야 잉여 속성 체크와 일반적인 구조적 할당 가능선 체클르 구분할 수 있다.
interface Room {
numDoors: number;
ceilingHeightFt: number;
}
function setDarkMode() {}
interface Options {
title: string;
darkMode?: boolean;
}
const o: Options1 = { darkmode: true, title: 'Ski Free' };
// ~~~~~~~~ 'darkmode' does not exist in type 'Options'...
const intermediate = { darkmode: true, title: 'Ski Free' };
const o: Options2 = intermediate; // OK
위에서 Options1에서는 에러가 발생하고, Options2에서는 에러가 발생하지 않는다. 왜냐하면 Options1에서는 뒤의 객체가 리터럴로 동작했기 때문이다. Options1의 경우 객체 리터럴로 동작하여 잉여 속성 체크가 적용되어 오류가 발생한다.
잉여 속성 체크를 원치않는다면, 인덱스 시그니처를 사용해 TS의 추가적인 속성을 예상할 수 있다.
interface Options {
darkMode?: boolean;
title: string;
[otherOptions: string]: unknown;
}
const o: Options = { darkmode: true }; // OK
12. 함수 표현식에 타입 적용하기
-
매개변수나 반환 값에 타입을 명시하기보다는 함수 표현식 전체에 타입 구문을 적용하는 것이 좋다.
타입스크립트에서는 함수 선언문보다 표현식을 권장한다. 아래처럼 한 번에 타입을 선언할 수 있기 때문이다.
type BinaryFn = (a: number, b: number) => number; const add: BinaryFn = (a, b) => a + b; const sub: BinaryFn = (a, b) => a - b; const mul: BinaryFn = (a, b) => a * b; const div: BinaryFn = (a, b) => a / b; -
만약 같은 시그니처를 반복적으로 작성한 코드가 있다면 함수 타입을 분리해내거나 이미 존재하는 타입을 찾아보자.
const checkedFetch: typeof fetch = async (input, init) => { const response = await fetch(input, init); if (!response.ok) { throw new Error('Request failed: ' + response.status); } // throw가 아니라 return을 한다면, checkedFetch의 return 타입은 Promise<Response | HTTPError>가 되어, 아래 fetch의 return타입과 달라 에러가 발생한다. return response; }위 코드에서, typeof fetch는 다음과 같다.
declare function fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
- 다른 함수의 시그니처를 참조하려면
typeof fuction을 사용하면 된다.
13. 타입과 인터페이스의 차이점 알기
- type
- 유니온 타입은 타입을 확장할 수 없다.
- interface
- 유니온 타입을 확장하지 못한다.
- 튜플에서 사용할 수 있는 concat같은 메서드를 사용할 수 없다. 이는 타입으로 하는 것이 좋다.
interface Tuple {
0:number;
1:number;
length:2;
}
const t : Tuple = [1,2];
위와 같이 튜플 타입을 억지로 만들 수 있긴 하지만, 권장하지 않는다.
- 보강 기능이 가능하다.
interface IState{
name : string;
}
interface IState{
email : string;
}
IState는 name과 email 두 개의 필드를 가진 인터페이스이다.
타입 선언 파일을 작성할 때, 선언 병합을 위해 반드시 인터페이스를 사용해야 하며, 표준을 따라야 한다.
- 사용하는 경우
- type : 프로젝트 내부적으로 사용되는 타입에 선언 병합이 발생하는 경우
- interface : API에 대한 타입을 작성하는 경우
- API가 변경될 떄 사용자가 interface를 통해 새로운 필드를 병합할 수 있어 유용
프로젝트에서 어떤 문법을 사용할 지 결정할 때, 한가지 일관된 스타일을 확립하고, 보강 기법이 필요한지 고려해야 한다.
14. 타입 연산과 제너릭 사용으로 반복 줄이기
DRY(Don’t Repeast Yourself) : 같은 코드는 반복하지 말라
다양한 방법들
-
extends 혹은 인터섹션 연산자(
&)를 이용한 확장interface Person { firstName: string; lastName: string; } interface PersonWithBirthDate extends Person { birth: Date; } type PersonWithBirthDate = Person & { birth: Date }; -
인덱싱하여 중복 제거
interface State { userId: string; pageTitle: string; recentFiles: string[]; pageContents: string; } type TopNavState = { userId: State['userId']; pageTitle: State['pageTitle']; recentFiles: State['recentFiles']; }; -
매핑된 타입
Picktype TopNavState = { [k in 'userId' | 'pageTitle' | 'recentFiles']: State[k] };k 제네릭 연산자를 사용한다. 위 코드는 아래와 같이 Pick을 사용할 수도 있다.
type TopNavState = Pick<State, 'userId' | 'pageTitle' | 'recentFiles'>;
Pick은 제너릭 타이븡로, 중복된 코드를 제거한다는 관점에서 Pick을 사용하는 것은 함수를 호출하는 것에 비유할 수 있다. 마치 함수에서 두 개의 매개변수 값을 받아 결과값을 반환하는 것처럼, Pick은 T와 K 두가지 타입을 받아 결과 타입을 리턴한다.
type Pick<T, K> = {[k in K]:T[k]}
T는 State, K는 ‘userId’ | ‘pageTitle’ | ‘recentFiles’
-
keyofkeyof는 타입을 받아서 속성 타입의 유니온을 반환한다.
interface Options { width: number; height: number; color: string; label: string; } type OptionsKeys = keyof Options; // Type is "width" | "height" | "color" | "label"-
응용
interface Options { width: number; height: number; color: string; label: string; } type OptionsUpdate = {[k in keyof Options]?: Options[k]};
-
위 OptionsUpdate 타입은 Options의 모든 타입의 속성을 선택적으로 만들었다. 위와 같은 코드는 아래처럼 Partial을 사용할 수 있다.
-
Partialtype OptionsUpdate = Partial<Options> -
typeofconst INIT_OPTIONS = { width: 640, height: 480, color: '#00FF00', label: 'VGA', }; type Options = typeof INIT_OPTIONS;값으로부터 타입을 만들 때 사용한다. 이 typeof는 Javascript의 typeof와 다르다는 점에서 유의해야 한다.
-
ReturnType : 리턴하는 값의 타입
function getUserInfo(userId: string) { // COMPRESS const name = 'Bob'; const age = 12; const height = 48; const weight = 70; const favoriteColor = 'blue'; // END return { userId, name, age, height, weight, favoriteColor, }; } // Return type inferred as { userId: string; name: string; age: number, ... } type UserInfo = ReturnType<typeof getUserInfo>;getUserInfo는 실제로 함수이므로, 이 함수의 타입을 얻기 위해 우선 typeof getUserInfo를 사용했다.
typeof getUserInfo는 다음과 같다.
function getUserInfo(userId: string): {
userId: string;
name: string;
age: number;
height: number;
weight: number;
favoriteColor: string;
}
요약
- 타입에 이름을 붙여서 반복을 피하고, extends를 이용해서 인터페이스 필드의 반복을 피해야 한다.
- 타입들 간의 매핑을 위해 TS가 제공한 도구들을 공부하면 좋다
- keof, typeof, 인덱싱, 매핑된 타입 등
- 제네릭 타입은 타입을 위한 함수와 같다. 타입을 반복하는 대신 제네릭 타입을 사용하여 타입들 간에 매핑을 하는 게 좋다.
- 제네릭타입을 제한하려면 extends를 사용한다.
- 표준 라이브러리에 정의된 Pick, Partial, ReturnType같은 제네릭타입에 익숙해져야 한다.
15장 : 동적 데이터에 인덱스 시그니처 사용하기
type Rocket = {[property: string]: string};
key값이 string이면 성립하기에, 범위가 너무 포괄적이다. 그래서 인덱스 시그니처는 런타임 때까지 객체의 속성을 알수 없을 경우엔 사용한다.
-
예시
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; }); }row의 타입을 알 수 없는 경우,
{[columnName:string]:string}으로 선언하여, key값은 string, value값은 string임을 명시한다.그리고, 사용하는 쪽에서는 as로 타입 단언을 한다.
interface ProductRow {
productId: string;
name: string;
price: string;
}
declare let csvData: string;
const products = parseCSV(csvData) as unknown as ProductRow[];
가능하다면 인터페이스, Record, 매핑된 타입 같은 인덱스 시그니처보다 정확한 타입을 사용하는 것이 좋다.
-
Record
type Student = Record<'name'|'school', string>;참고로, Pick, Record 등의 유틸리티 타입은 interface에서는 사용하지 못하고 type에만 적용된다. interface는 주로 객체의 모양을 정의하는 데 사용하며, 상속이나 확장을 위해 설계되었다.
-
매핑된 타입
type Student = {[k in 'name'|'school'] : string}
16장 : number 인덱스 시그니처보다는 Array, 튜플, ArrayLike 사용하기
이해가 잘 안된다.
-
배열은 객체이므로 숫자가 아니라 문자열이다. 인덱스 시그니처로 사용된 number타입은 버그를 잡기 위한 순수 타입스크립트 코드이다.
-
인덱스 시그니처에 number를 사용하기보다 Array나 튜플, 또는 ArrayLike타입을 사용하는 것이 좋다.
const xs = [1, 2, 3]; const tupleLike: ArrayLike<string> = { '0': 'A', '1': 'B', length: 2, }; // OK
17장 : 변경 관련된 오류 방지를 위해 readonly 사용하기
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;
}
위 코드서 arr는 reaonly number[]타입인데, pop이라는 변경 메서드를 실행해서 에러가 발생한다.
readonly타입은 변경 가능한 타입의 부분 집합이다. 그래서, 변경 가능한 배열을 readonly배열에 할당할 수 있다. 하지만 그 반대는 불가능하다.
const a: number[] = [1, 2, 3];
const b: readonly number[] = a;
const c: number[] = b;
// ~ Type 'readonly number[]' is 'readonly' and cannot be
// assigned to the mutable type 'number[]'
- 만약 함수가 매개변수를 수정하지 않는다면 readonly로 선언하는 것이 좋다. reaonly매개변수는 인터페이스를 명확하게 하며, 매개벼누가 변경되는 것을 방지한다.
- reaonly를 사용하면 변경하면서 발생하는 오류를 방지할 수 있고, 변경이 발생하는 코드도 쉽게 찾을 수 있다.
- const와 readonly의 차이를 이해해야 한다.
- reaonly는 얕게 동작한다.
18. 매핑된 타입을 사용하여 값을 동기화하기
const REQUIRED_UPDATE: {[key in keyof ScatterProps]:boolean} => {
// ..
};
- 매핑된 타입을 사용해서 관련된 값과 타입을 동괴화하도록 합니다.
- 인터페이스에 새로운 속성을 추가할 떄, 선택을 강제하도록 매핑된 타입을 고려해야 합니다.
3. 타입 추론
19. 추론 가능한 타입을 사용해 장황한 코드 방지하기
-
타입스크립트가 타입을 추론할 수 있다면 타입 구문을 작성하지 않는 게 좋습니다.
-
이상적인 경우 함수/메서드의 시그니처에는 타입 구문이 있지만, 함수 내의 지역 변수에는 타입 구문이 없습니다.
-
추론될 수 있는 경우라도 객체 리터럴과 함수 반환에는 타입 명시를 고려해야 합니다. 이는 내부 구현의 오류가 사용자 코드 위치에 나타내는 것을 방지해 줍니다.
interface Vector2D { x: number; y: number; } function add(a: Vector2D, b: Vector2D): Vector2D { return { x: a.x + b.x, y: a.y + b.y }; }const cache: {[ticker: string]: number} = {}; function getQuote(ticker: string): Promise<number> { if (ticker in cache) { return cache[ticker]; // ~~~~~~~~~~~~~ Type 'number' is not assignable to 'Promise<number>' } // COMPRESS return Promise.resolve(0); // END }