타입스크립트가 코드를 해석해 나가는 동작을 의미
let x = 3;
x에 대한 타입을 따로 지정하지 않더라도 일단 x는 number로 간주하는 것을 타입추론이라고 한다.
타입추론이 일어나는 경우
타입스크립트가 타입 추론을 통해 판단할 수 있는 타입의 범주를 넘는 경우, 더 이상 추론하지 않도록 지시할 수 있. 프로그래머가 원하는 임의의 타입을 값에 할당하고 싶을 때, 프로그래머가 타입스크립트보다 타입에 대해 더 잘 이해하고 있는 상황일 때 필요한 것이 바로 타입 단언이다.
// 예제
function someFunc(val: string | number, isNumber: boolean) {
// some logics
if (isNumber) {
val.toFixed(2); // Error - TS2339: ... Property 'toFixed' does not exist on type 'string'.
}
}
toFixed()
메서드는 숫자를 고정 소수점 표기법으로 표시하는 숫자 Number prototype 메소드이다.
예제 해석
val
은 유니언 타입으로 문자열(String)이거나 숫자(Number)일 수 있는 상황isNumber
는 불린(Boolean)이며, 우리는 이름을 통해 숫자 여부를 확인하는 값이라는 것을 추론할 수 있다.isNumber
가 true
일 경우 val
은 숫자일 것이고, 이에 toFixed
를 사용할 수 있음을 알 수 있다.하지만 타입스크립트는 isNumber
라는 이름만으로 위 내용을 추론할 수 없기 때문에 “val
이 문자열인 경우 toFixed
를 사용할 수 없다”고 (컴파일 단계에서) 에러를 반환 한 것이다.
따라서 isNumber
가 true
일 때 val
이 숫자임을 단언할 수 있다.
function someFunc(val: string | number, isNumber: boolean) {
// some logics
if (isNumber) {
// 1. 변수 as 타입
(val as number).toFixed(2);
// Or
// 2. <타입>변수
// (<number>val).toFixed(2);
}
}
(<number>val)
은 JSX를 사용하는 경우 특정 구문 파싱에서 문제가 발생할 수 있으며, 결과적으로 .tsx 파일에서는 전혀 사용할 수 없다.!
를 사용하는 Non-null 단언 연산자를 통해 피연산자가 Nullish(null
이나 undefined
) 값이 아님을 단언할 수 있는데, 변수나 속성에서 간단하게 사용할 수 있기 때문에 유용하다.
// Error - TS2533: Object is possibly 'null' or 'undefined'.
function fnA(x: number | null | undefined) {
return x.toFixed(2);
}
매개 변수 x
는 함수 내에서 toFixed
를 사용하는 숫자 타입으로 처리되지만 null
이나 undefined
일 수 있기 때문에 에러가 발생한다.
// Non-null assertion operator
function fnE(x: number | null | undefined) {
return x!.toFixed(2);
}
!
를 사용하는 Non-null 단언 연산자를 이용해 간단하게 Nullish(null
이나 undefined
) 값이 아님을 단언할 수 있다.
타입을 매번 보장하기 위해 타입 단언을 여러 번 사용하게 되는 경우가 있다.
타입 가드를 제공하면 타입스크립트가 추론 가능한 특정 범위(scope)에서 타입을 보장할 수 있다.
사용자 정의 타입 가드는 value is Type
형태의 반환 타입을 갖는 함수로 정의한다.
// 타입 가드
function isNumber(val: string | number): val is number {
return typeof val === 'number';
}
function someFunc(val: string | number) {
if (isNumber(val)) {
val.toFixed(2);
isNaN(val);
} else {
val.split('');
val.toUpperCase();
val.length;
}
}
위의 예제는 val is number
으로 타입 단언을 해준 것이다.
위 방식뿐만 아니라 제공 가능한 타입 가드들
: typeof
, in
, instanceof
비교적 단순한 로직에서 추천되는 방식
typeof
연산자는number
,string
,boolean
, 그리고symbol
만 타입 가드로 인식할 수 있다.
in
연산자의 우변 객체(val)는any
타입이어야 한다.
인터페이스(Interface)는 타입스크립트 여러 객체를 정의하는 일종의 규칙이며 구조이다.
interface
키워드와 함께 사용한다.
interface IUser {
name: string,
age: number,
isAdult: boolean
}
let user1: IUser = {
name: 'Neo',
age: 123,
isAdult: true
};
// Error - TS2741: Property 'isAdult' is missing in type '{ name: string; age: number; }' but required in type 'IUser'.
let user2: IUser = {
name: 'Evan',
age: 456
};
user2는 isAdult를 사용하지 않았기 때문에 에러가 발생한다. 속성에 ?
를 사용하면 선택적 속성으로 정의할 수 있다.
interface IUser {
name: string,
age: number,
isAdult?: boolean // Optional property
}
// `isAdult`를 초기화하지 않아도 에러가 발생하지 않습니다.
let user: IUser = {
name: 'Neo',
age: 123
};
readonly
키워드를 사용하면 초기화된 값을 유지해야 하는 읽기 전용 속성을 정의할 수 있다.
interface IUser {
readonly name: string,
age: number
}
// 초기화
let user: IUser = {
name: 'Neo',
age: 36
};
user.age = 85; // Ok
user.name = 'Evan'; // Error - TS2540: Cannot assign to 'name' because it is a read-only property.
만약 모든 속성이 readonly
일 경우, 유틸리티(Utility)나 단언(Assertion) 타입을 활용할 수 있다.
// All readonly properties
interface IUser {
readonly name: string,
readonly age: number
}
// Readonly Utility
interface IUser {
name: string,
age: number
}
let user: Readonly<IUser> = {
name: 'Neo',
age: 36
};
user.age = 85; // Error
user.name = 'Evan'; // Error
// Type assertion
let user = {
name: 'Neo',
age: 36
} as const;
user.age = 85; // Error
user.name = 'Evan'; // Error
// as const: let 변수의 경우에도 const처럼 literal type으로 추론해 줄 수 있는데, 그 때 사용하는 것이 as const 이다.