이펙티브 타입스크립트 (댄 밴더캄 지음) 를 읽고 정리
📍요약
✅변수를 뽑아서 별도로 선언했을 때 에러가 발생한다면 타입 선언을 추가해야한다
✅변수가 정말 상수라면 상수 단언(at const)을 사용해야 한다
- 단, 상수 단언을 사용하면 정의한 곳이 아닌, 사용한 곳에서 에러가 발생할 수 있으므로 주의
📍튜플 사용시 타입 추론 과정에서 유의점
타입스크립트의 튜플이 배열을 만드는 [] 를 같이 사용하기 때문에 배열로 혼동되는 에러가 발생할 수 있다
- 예시
// Parameter is a (latitude, longitude) pair.
function panTo(where: [number, number]) { /* ... */ }
panTo([10, 20]); // OK
const loc = [10, 20];
panTo(loc); // 타입이 튜플이 아닌 number[] 이므로 에러 발생
// ~~~ Argument of type 'number[]' is not assignable to
// parameter of type '[number, number]'
[10, 20] 은 튜플(길이가 고정)이기도 하지만, 배열(길이가 정해지지 않음)이기도 하다
- 따라서 타입 추론시 우선 number[]로 추론된다
이 문제가 string과 리터럴 타입인 경우에는 const 키워드를 사용하여 해결할 수 있다
그런데 const는 이미 사용했다. 어떻게 해결할 수 있을까?
⭐타입스크립트가 타입을 추론하지 않도록 타입 선언을 제공하면 된다
const loc: [number, number] = [10, 20];
⭐또 다른 해결방법은 at const를 붙여 상수 문맥을 제공하는 것이다
- const는 단지 그 값이 가리키는 참조가 변하지 않는 얕은(shallow) 상수
- as const는 그 값이 내부까지 (deeply) 상수 -> readonly 붙임
그래서 일차적으로는 타입 에러가 발생한다
- as const 를 사용한 readonly [10, 20] 가 과도하게 정확하기 때문이다
- pan 함수의 매개변수의 타입은 단지 [number, number] 튜플이므로 [10, 20] 대신 [20, 30] 도 가능
더 깊게 들여다보면
- [10, 20] as const (튜플)은 엄밀히 ReadonlyArray가 된다
(Array의 부분집합)
- ReadonlyArray 타입에 Array 타입을 할당하는 것은 가능하나 그 역은 성립 X
- 예시
// Parameter is a (latitude, longitude) pair.
function panTo(where: [number, number]) { /* ... */ }
const loc = [10, 20] as const; // 타입 : readonly [10, 20]
panTo(loc);
// ~~~ Type 'readonly [10, 20]' is 'readonly'
// and cannot be assigned to the mutable type '[number, number]'
해결책 2가지를 떠올려볼 수 있다
1️⃣panTo 함수 매개변수인 where에 readonly를 부여한다
- 예시
function panTo(where: readonly [number, number]) { /* ... */ }
const loc = [10, 20] as const;
panTo(loc); // OK
2️⃣별도의 타입구문을 사용한다
- 예시
type WhereType = [number, number];
function panTo(where: WhereType) { /* ... */ }
const loc: WhereType = [10, 20];
panTo(loc); // OK
📍객체 사용시 타입 추론 과정에서 유의점
- 객체에서도 타입 추론시 비슷한 문제를 찾을 수 있다
type Language = 'JavaScript' | 'TypeScript' | 'Python';
interface GovernedLanguage {
language: Language;
organization: string;
}
function complain(language: GovernedLanguage) { /* ... */ }
complain({ language: 'TypeScript', organization: 'Microsoft' }); // OK
const ts = {
language: 'TypeScript',
organization: 'Microsoft',
};
complain(ts); // ts의 language 프로퍼티의 타입이 달라서 에러 발생
// ts의 타입
// const ts: {
// language: string;
// organization: string;
// }
이 문제도 타입 선언을 추가하거나, 상수 단언 (as const)을 사용하여 해결할 수 있다
type Language = 'JavaScript' | 'TypeScript' | 'Python';
interface GovernedLanguage {
language: Language;
organization: string;
}
function complain(language: GovernedLanguage) { /* ... */ }
complain({ language: 'TypeScript', organization: 'Microsoft' }); // OK
// 1. 타입 선언 추가
const ts1: GovernedLanguage = {
language: 'TypeScript',
organization: 'Microsoft',
};
complain(ts1); // OK
// 2. 상수 단언 추가
const ts2 = {
language: 'TypeScript',
organization: 'Microsoft',
} as const;
complain(ts2); // OK
객체 as const는 여전히 Object 타입
(튜플의 경우 as const 사용하면 ReadonlyArray로 바뀜)