9장 타입 제한자
9.1 top 타입
top 타입은 시스템에서 가능한 모든 값을 나타낸다. 모든 타입은 top 타입에 할당할 수 있다.
9.1.1 any 다시 보기
any 타입은 top 타입처럼 작동할 수 있다. any는 일반적으로 console.log
의 매개변수와 같이 모든 타입의 데이터를 받는 위치에서 사용한다.
any는 타입스크립트가 해당 값에 대한 할당 가능성 또는 멤버에 대해 타입 검사를 수행하지 않도록 명시적으로 지시하는 문제점을 갖고 있다. 이러한 안정성 부족은 타입 검사기를 건너뛰려고 할 때 유용하지만 타입 검사를 비활성화하면 해당 값에 대한 타입스크립트의 유용성이 줄어든다.
어떤 값이든 될 수 있음을 나타내기 위해서는 unknown 타입이 안전하다.
9.1.2 unknown
unknown 타입과 any 타입의 주요 차이점은 unknown 타입의 값을 훨씬 더 제한적으로 취급한다는 점이다.
- 타입스크립트는 unknown 타입 값의 속성에 직접 접근할 수 없다.
- unknown 타입은 top 타입이 아닌 타입에는 할당할 수 없다.
unknown 타입 값의 속성에 접근하려고 시도하면 타입 오류를 보고한다.
function greetComedian(name: unknown) {
console.log(`Announcing ${name.toUpperCase()}!`);
// ERROR: 'name'은(는) 'unknown' 형식입니다.
}
unknown 타입인 name
에 접근할 수 있는 방법은 instanceof, typeof, 타입 어서션을 사용하는 것처럼 값의 타입이 제한된 경우이다.
unknown에서 string으로 타입을 좁히기 위해 typeof를 사용한다.
function greetComedianSafety(name: unknown) {
if (typeof name == 'string') {
console.log(`Announcing ${name.toUpperCase()}!`); // OK
} else {
console.log("Well, I'm off.");
}
}
greetComedianSafety('Betty Whire'); // LOG: "Announcing BETTY WHIRE!"
greetComedianSafety({}); // LOG: "Well, I'm off."
9.2 타입 서술어
제한된 검사로 instanceof, typeof와 같은 자바스크립트 구문을 사용해 타입을 좁히는 방법을 직접 사용할 때는 괜찮지만 로직을 함수로 감싸면 타입을 좁힐 수 없게 된다.
function isNumberOrString(value: unknown) {
return ['number', 'string'].includes(typeof value);
}
function logValueIfExists(value: number | string | null | undefined) {
if (isNumberOrString(value)) {
value.toString(); // ERROR: 'value'은(는) 'null' 또는 'undefined'일 수 있습니다.
} else {
console.log('Value does not exist: ', value);
}
}
타입스크립트는 isNumberOrString
이 boolean 값을 반환한다는 사실만 알 수 있고 인수의 타입을 좁히기 위함이라는 건 알 수 없다.
타입 서술어(사용자 정의 타입 가드): 인수가 특정 타입인지 여부를 나타내기 위해 boolean 값을 반환하는 함수를 위한 구문
타입 서술어는 일반적으로 매개변수로 전달된 인수가 매개변수의 타입보다 더 구체적인 타입인지 여부를 나타낼 때 사용한다. 타입 서술어의 반환 타입은 매개변수의 이름, is 키워드, 특정 타입으로 선언할 수 있다.
function typePredicate(input: WideType): input is NarrowType {
// ...
}
isNumberOrString
함수에서 value
를 value is number | string
으로 명시적으로 변경하면 명시적 반환 타입을 가질 수 있다.
function isNumberOrString(value: unknown): value is number | string {
return ['number', 'string'].includes(typeof value);
}
function logValueIfExists(value: number | string | null | undefined) {
if (isNumberOrString2(value)) {
value.toString(); // value: string | number
} else {
console.log('Value does not exist: ', value); // value: null | undefined
}
}
타입 서술어는 한 인터페이스의 인스턴스로 알려진 객체가 더 구체적인 인터페이스의 인스턴스인지 여부를 검사할 때 자주 사용한다.
interface Comedian {
funny: boolean;
}
interface StandupComedian extends Comedian {
routine: string;
}
function isStandupComedian(value: Comedian): value is StandupComedian {
return 'routine' in value;
}
function workWithComedian(value: Comedian) {
if (isStandupComedian(value)) {
console.log(value.routine); // value: StandupComedian
}
console.log(value.routine); // ERROR: 'Comedian' 형식에 'routine' 속성이 없습니다.
}
isStandupComedian
타입 가드는 Comedian
이 구체적으로 StandupComedian
인지 여부를 확인할 때 사용한다.
타입 서술어는 false 조건에서 타입을 좁히기 때문에 타입 서술어가 입력된 타입 이상을 검사하는 경우 예상치 못한 결과를 얻을 수 있다.
function isLongString(input: string | undefined): input is string {
return !!(input && 7 <= input.length);
}
function workWithText(text: string | undefined) {
if (isLongString(text)) {
console.log('Long text: ', text.length); // text: string
} else {
console.log('Short text: ', text?.length); // ERROR: 'never' 형식에 'length' 속성이 없습니다.
}
}
isLongString
타입 서술어는 input
매개변수가 undefined 또는 길이가 7보다 작은 string인 경우 false를 반환한다. else 문(false 조건)은 text
를 undefined 타입으로 좁힌다.
타입 서술어는 속성이나 값의 타입을 확인하는 것 이상을 수행해 잘못 사용하기 쉬우므로 가능하면 피하는 것이 좋다. 대부분은 간단한 타입 서술어만으로도 충분하다.
9.3 타입 연산자
9.3.1 keyof
자바스크립트 객체는 일반적으로 string 타입인 동적값을 사용하여 검색된 멤버를 갖는다. string 같은 포괄적인 원시 타입을 사용하면 컨테이너 값에 대해 유효하지 않은 키가 허용된다.
interface Ratings {
audience: number;
critics: number;
}
function getRating(ratings: Ratings, key: string): number {
return ratings[key];
// ERRPR: 'string' 형식의 식을 'Ratings' 인덱스 형식에 사용할 수 없으므로 요소에 암시적으로 'any' 형식이 있습니다.
// 'Ratings' 형식에서 'string' 형식의 매개 변수가 포함된 인덱스 시그니처를 찾을 수 없습니다.
}
const ratings: Ratings = { audience: 66, critics: 84 };
getRating(ratings, 'audience'); // OK
getRating(ratings, 'not valid'); // 허용되지만 사용하면 안 됨
타입 string은 Ratings
인터페이스에서 속성으로 허용되지 않는 값을 허용하고 Ratings
는 string 키를 허용하는 인덱스 시그니처를 선언하지 않는다.
다른 옵션은 허용되는 키를 위한 리터럴 유니언 타입을 사용하는 것이다. 컨테이너 값에 존재하는 키만 적절하게 제한하는 것이 더 정확하다.
interface Ratings {
audience: number;
critics: number;
}
function getRating(ratings: Ratings, key: 'audience' | 'critics'): number {
return ratings[key]; // OK
}
const ratings: Ratings = { audience: 66, critics: 84 };
getRating(ratings, 'audience'); // OK
getRating(ratings, 'not valid'); // ERROR: '"not valid"' 형식의 인수는 '"audience" | "critics"' 형식의 매개 변수에 할당될 수 없습니다.
인터페이스에 수십 개 이상의 멤버가 있다면 모두 입력하고 유지하기는 번거로울 수 있다.
keyof: 기존에 존재하는 타입을 사용하고 해당 타입에 허용되는 모든 키의 조합을 반환하는 연산자
interface Ratings {
audience: number;
critics: number;
}
function getCountKeyof(ratings: Ratings, key: keyof Ratings): number {
return ratings[key]; // OK
}
const ratings: Ratings = { audience: 66, critics: 84 };
getCountKeyof(ratings, 'audience'); // OK
getCountKeyof(ratings, 'not valid'); // ERROR: '"not valid"' 형식의 인수는 'keyof Ratings' 형식의 매개 변수에 할당될 수 없습니다.
keyof는 존재하는 타입의 키를 바탕으로 유니언 타입을 생성하는 기능이다.
9.3.2 typeof
typeof: 제공되는 값의 타입을 반환하는 연산자
값의 타입을 수동으로 작성하는 것이 복잡할 때 사용하면 유용하다.
typeof 타입 연산자는 시각적으로 주어진 값이 어떤 타입인지를 반환할 때 사용하는 런타임 typeof 연산자처럼 보이지만 이 둘은 차이가 있다.
- 자바스크립트 typeof: 타입에 대한 문자열 이름을 반환하는 런타임 연산자
- 타입스크립트 typeof: 타입스크립트에서만 사용할 수 있으며 컴파일된 자바스크립트 코드에는 나타나지 않음
const ratings = {
imdb: 8.4,
metacritic: 82,
};
function logRating(key: keyof typeof ratings) {
console.log(ratings[key]);
}
logRating('imdb'); // OK
logRating('invalid'); // ERROR: '"invalid"' 형식의 인수는 '"imdb" | "metacritic"' 형식의 매개 변수에 할당될 수 없습니다.
logRating
함수는 ratings
값의 키 중 하나를 받는다. 인터페이스를 생성하는 것 대신 keyof typeof를 사용해서 키가 ratings
값 타입의 키 중 하나임을 나타낸다.