- 타입스크립트는 타입을 추론할 때 단순히 값만 고려하지 않음
- 값이 존재하는 곳의 문맥까지 고려
- 문맥을 고려해 타입을 추론할 때 이상한 결과가 나올 수도
- 자바스크립트와 타입스크립트의 차이
- 자바스크립트
- function setLanguage(language: string) { /* ... */ } setLanguage('JavaScript'); // OK let language = 'JavaScript'; setLanguage(language); // OK
- 타입스크립트
- inline 형태에서 TS는 함수 선언을 통해 매개변수가 Language타입이어야 함을 앎
- 값을 변수로 분리하면, TS는 할당 시점에 타입을 추론
- type Language = 'JavaScript' | 'TypeScript' | 'Python'; function setLanguage(language: Language) { /* ... */ } setLanguage('JavaScript'); // OK let language = 'JavaScript'; setLanguage(language); // ~~~~~~~~ Argument of type '**string**' is not assignable // to parameter of type 'Language'
- 문제 해결 방법
- 타입 선언에서 language의 가능한 값을 제한 하는 것
- let language: Language = 'JavaScript';
- language를 상수로 만드는 것
- const를 사용하여 타입 체커에게 language는 변경할 수 없음을 알려줌
- const language = 'JavaScript';
→ 사용되는 문맥으로부터 값을 분리함. 문맥과 값을 분리하면 문제 발생할 수 있음
문맥의 소실로 인해 발생하는 오류
튜플 사용 시
type Language = 'JavaScript' | 'TypeScript' | 'Python';
function setLanguage(language: Language) { /* ... */ }
// Parameter is a (latitude, longitude) pair.
function panTo(where: [number, number]) { /* ... */ }
panTo([10, 20]); // OK
const loc = [10, 20];
panTo(loc);
// ~~~ Argument of type 'number[]' is not assignable to
// parameter of type '[number, number]'
- 문맥과 값을 분리할 경우, number[]로 추론하기 때문에 많은 배열이 이와 맞지 않는 수의 요소를 가지므로 튜플 타입에 할당할 수 없음
문제 해결 방법
- 타입 선언을 제공하는 방법
- const loc: [number, number] = [10, 20]; panTo(loc); // OK
- ‘상수 문맥’을 제공하는 방법
- const는 단지 값이 가리키는 참조가 변하지 않는 얕은 상수
- as const는 그 값이 내부까지 상수
- const loc = [10, 20] as const; panTo(loc); // ~~~ Type 'readonly [10, 20]' is 'readonly' // and cannot be assigned to the mutable type '[number, number]'
- ponTo함수에도 readonly 추가
- function panTo(where: readonly [number, number]) { /* ... */ } const loc = [10, 20] as const; panTo(loc); // OK
- as const는 문맥 손실과 관련한 문제를 해결할 수 있지만, 타입 정의에 실수가 있다면 오류는 타입 정의가 아닌 호출되는 곳에서 발생
- 여러 겹 중첩된 객체에서 오류가 발생한다면 원인 파악의 어려움
function panTo(where: readonly [number, number]) { /* ... */ }
const loc = [10, 20, 30] as const; // error is really here.
panTo(loc);
// ~~~ Argument of type 'readonly [10, 20, 30]' is not assignable to
// parameter of type 'readonly [number, number]'
// Types of property 'length' are incompatible
// Type '3' is not assignable to type '2'
⇒ 타입 시그니처를 수정할 수 없는 경우 : 타입 구문 사용
객체 사용 시
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);
// ~~ Argument of type '{ language: string; organization: string; }'
// is not assignable to parameter of type 'GovernedLanguage'
// Types of property 'language' are incompatible
// Type 'string' is not assignable to type 'Language'
문제 해결 방법
- 타입 선언을 추가 (const ts: GovernedLanguage = …)
- 상수 단언(as const)사용
콜백 사용 시
- 콜백을 다른 함수로 전달 시 TS는 콜백의 매개변수 타입을 추론하기 위해 문맥 사용
function callWithRandomNumbers(fn: (n1: number, n2: number) => void) {
fn(Math.random(), Math.random());
}
callWithRandomNumbers((a, b) => {
a; // Type is number
b; // Type is number
console.log(a + b);
});
- 콜백을 상수로 뽑아내면 문맥이 소실되고 noImplicitAny 오류 발생
const fn = (a, b) => {
// ~ Parameter 'a' implicitly has an 'any' type
// ~ Parameter 'b' implicitly has an 'any' type
console.log(a + b);
}
callWithRandomNumbers(fn);
문제 해결 방법
- 매개변수에 타입 구문을 추가
- const fn = (a: number, b: number) => { console.log(a + b); } callWithRandomNumbers(fn);
- 전체 함수 표현식에 타입을 적용
- type Tfn = (a: number, b: number) => void; const fn: Tfn = (a, b) => { console.log(a + b); }
요약
- 타입 추론에서 문맥이 어떻게 쓰이는지 주의해야 함
- 변수를 뽑아서 별도로 선언했을 때 오류가 발생한다면 타입 선언을 추가해야 함
- 변수가 정말로 상수라만 상수 단언(as const)사용해야함
- 상수 단언을 사용하면 정의한 곳이 아니라 사용한 곳에서 오류가 발생하므로 주의해야 함