자주 사용되는 타입
- Published on
- 이 문서는 타입스크립트에서 자주 사용되는 타입들에 대해 설명하며 기본 타입부터 복잡한 객체 타입, 열거형, 리터럴 타입 등 다양한 타입 시스템의 요소를 다룹니다.
Table of Contents
이 장에서는 자바스크립트 코드를 작성할 때 자주 만나게 되는 값 타입들과 그에 해당하는 타입스크립트에서의 표현 방법을 설명합니다. 여기서 다루는 내용은 모든 가능한 타입을 다루진 않지만, 앞으로 나올 장들에서 다양한 타입 이름과 사용 방법에 대해 더 배울 수 있습니다.
타입은 단순한 타입 주석 이상의 곳에 나타날 수 있습니다. 타입 자체에 대해 배우는 동안, 이 타입들을 어떻게 참조하여 새로운 구조를 형성할 수 있는지도 함께 배우게 됩니다.
자바스크립트나 타입스크립트 코드를 작성하며 가장 기본적이고 흔하게 접하는 타입들을 살펴볼 것입니다. 이들은 더 복잡한 타입들을 구축하는 핵심 요소가 됩니다.
일반적 타입
이번 장에서는 자바스크립트 코드에서 자주 등장하는 값 타입들과 타입스크립트에서 해당 타입들을 어떻게 표현하는지 알아봅니다. 여기서 다루는 내용은 모든 가능한 타입을 포함하지는 않지만, 추후 장에서 더 다양한 타입의 이름과 활용 방법을 배울 예정입니다.
타입은 타입 주석만이 아닌 여러 곳에서 나타날 수 있습니다. 타입들에 대해 배우는 동안 이러한 타입들을 참조하여 새로운 구조를 만드는 방법도 함께 배우게 됩니다.
자바스크립트나 타입스크립트 코드를 작성할 때 만나게 되는 가장 기본적이고 흔한 타입들을 먼저 살펴보겠습니다. 이 타입들은 더 복잡한 타입들을 만드는데 있어 핵심적인 구성 요소가 됩니다.
기본 타입: string, number, boolean
자바스크립트는 세 가지 매우 흔히 사용되는 기본 타입을 가집니다. string
, number
, boolean
입니다. 각각은 타입스크립트에서도 동일한 이름을 가진 타입과 대응됩니다. 예상할 수 있듯이, 이 이름들은 자바스크립트의 typeof
연산자를 사용했을 때 그 값의 타입으로 나타나는 것과 같습니다.
string
(문자열)은 "안녕, 세상"과 같은 문자열 값을 나타냅니다.number
(숫자)는 42와 같은 숫자를 의미합니다. 자바스크립트는 정수에 대한 특별한 런타임 값을 가지고 있지 않아,int
나float
와 같은 것은 없으며 모든 것은 단순히 숫자로 표현됩니다.boolean
(불리언)은true
(참)과false
(거짓)의 두 값에 사용됩니다.
대문자로 시작하는
String
,Number
,Boolean
타입 이름도 문법적으로 사용할 수 있지만 코드에서 매우 드물게 등장하는 몇몇 특별한 내장 타입을 가리킵니다. 타입에 대해string
,number
,boolean
을 항상 사용하세요.
배열
[1, 2, 3]과 같은 배열 타입을 지정하려면 number[]
문법을 사용할 수 있습니다. 이 문법은 모든 타입에 적용될 수 있어, 예를 들어 string[]
은 문자열의 배열을 의미합니다. Array<number>
와 같이 표현되기도 하는데, 이는 같은 의미입니다. T<U>
문법에 대해서는 제네릭을 다룰 때 더 배우게 될 것입니다.
[number]
는 다른 것을 의미합니다. 튜플 섹션을 참조하세요.
any
타입스크립트는 특별한 타입인 any
를 제공합니다. 이 타입은 특정 값이 타입 검사 오류를 일으키는 것을 원하지 않을 때 사용할 수 있습니다.
값이 any
타입일 때, 그것의 어떤 속성에도 접근할 수 있으며(그 결과도 any
타입이 됩니다), 함수처럼 호출할 수 있고 어떤 타입의 값에도 할당하거나 그로부터 할당받을 수 있으며 문법적으로 허용되는 거의 모든 것을 할 수 있습니다.
let obj: any = { x: 0 };
// 다음 코드 줄 중 어느 것도 컴파일러 오류를 발생시키지 않습니다.
// `any`를 사용하면 모든 타입 검사가 비활성화되며,
// 당신이 타입스크립트보다 환경을 더 잘 이해하고 있다고 가정합니다.
obj.foo();
obj();
obj.bar = 100;
obj = "hello";
const n: number = obj;
any
타입은 특정 코드 줄이 문제 없다고 타입스크립트를 설득하기 위해 긴 타입을 작성하고 싶지 않을 때 유용합니다.
noImplicitAny
타입을 지정하지 않고 타입스크립트가 컨텍스트로부터 유추할 수 없는 경우, 컴파일러는 일반적으로 any
로 기본 설정합니다.
그러나 any
는 타입 검사가 이루어지지 않기 때문에 이를 피하려는 것이 좋습니다. 컴파일러 플래그 noImplicitAny
를 사용하여 암시적 any
를 오류로 표시할 수 있습니다.
변수에 대한 타입
const
, var
, let
을 사용해 변수를 선언할 때, 선택적으로 타입 주석(annotation)을 추가하여 변수의 타입을 명시적으로 지정할 수 있습니다.
let myName: string = "Alice";
타입스크립트는
int x = 0;
과 같은 "왼쪽에 타입을 두는" 선언 방식을 사용하지 않습니다. 타입 주석은 항상 타입이 지정되는 대상 뒤에 위치합니다.
그러나 대부분의 경우 이는 필요하지 않습니다. 가능한 경우 타입스크립트는 코드 내의 타입을 자동으로 추론하려고 합니다. 예를 들어, 변수의 타입은 초기화하는 값의 타입을 기반으로 추론됩니다.
// 타입 주석이 필요 없음 -- 'myName'은 'string'으로 추론됨
let myName = "Alice";
대부분의 경우 명시적으로 추론 규칙을 배울 필요가 없습니다. 시작하는 단계라면 생각하는 것보다 더 적은 타입 주석을 사용해 보세요. 타입스크립트가 코드를 이해하는 데 필요한 타입 주석이 얼마나 적은지 놀랄 수 있습니다.
함수
함수는 자바스크립트에서 데이터를 주고받는 주요 수단입니다. 타입스크립트는 함수의 입력값과 출력값 타입을 지정할 수 있게 합니다.
매개변수 타입 주석
함수를 선언할 때 각 매개변수 뒤에 타입 주석을 추가하여 함수가 받아들일 매개변수의 타입을 선언할 수 있습니다. 매개변수 타입 주석은 매개변수 이름 뒤에 위치합니다.
// 매개변수 타입 주석
function greet(name: string) {
console.log("안녕, " + name.toUpperCase() + "!!");
}
매개변수에 타입 주석이 있으면 해당 함수에 전달된 인수가 검사됩니다.
// 실행되면 런타임 오류가 발생할 것입니다!
greet(42);
// 'number' 타입의 인수는 'string' 타입의 매개변수에 할당할 수 없습니다.
매개변수에 타입 주석이 없더라도 타입스크립트는 여전히 올바른 수의 인수가 전달되었는지 확인합니다.
반환 타입 주석
반환 타입 주석도 추가할 수 있습니다. 반환 타입 주석은 매개변수 목록 뒤에 나타납니다.
function getFavoriteNumber(): number {
return 26;
}
변수 타입 주석과 마찬가지로 대부분의 경우 반환 타입 주석이 필요하지 않습니다. 타입스크립트는 함수의 반환 문에 기반하여 함수의 반환 타입을 추론하기 때문입니다. 위 예제의 타입 주석은 아무 것도 변경하지 않습니다. 일부 코드베이스는 문서 목적으로 우발적인 변경을 방지하거나 개인적인 선호에 따라 명시적으로 반환 타입을 지정합니다.
프로미스를 반환하는 함수
프로미스를 반환하는 함수의 반환 타입을 주석하고 싶다면 Promise
타입을 사용해야 합니다.
async function getFavoriteNumber(): Promise<number> {
return 26;
}
익명 함수
익명(Anonymous) 함수는 함수 선언과는 약간 다릅니다. 함수가 타입스크립트가 호출 방식을 결정할 수 있는 위치에 나타날 때 그 함수의 매개변수는 자동으로 타입이 부여됩니다.
예를 들어보겠습니다.
const names = ["Alice", "Bob", "Eve"];
// 함수에 대한 문맥적 타이핑 - 매개변수 s는 문자열 타입으로 추론됩니다
names.forEach(function (s) {
console.log(s.toUpperCase());
});
// 문맥적 타이핑은 화살표 함수에도 적용됩니다
names.forEach((s) => {
console.log(s.toUpperCase());
});
매개변수 s
에 타입 주석이 없더라도 타입스크립트는 forEach
함수의 타입과 배열의 추론된 타입을 사용하여 s
가 가질 타입을 결정했습니다.
이 과정을 문맥적 타이핑이라고 합니다. 함수가 발생한 문맥이 어떤 타입을 가져야 하는지 알려주기 때문입니다.
추론 규칙과 비슷하게 이 과정을 명시적으로 배울 필요는 없지만, 이러한 일이 발생한다는 것을 이해하는 것이 타입 주석이 필요 없을 때를 알아차리는 데 도움이 될 수 있습니다. 나중에 값이 발생하는 문맥이 그 값의 타입에 어떤 영향을 미칠 수 있는지에 대한 더 많은 예를 볼 것입니다.
객체 타입
기본 타입 외에 가장 흔히 만나게 되는 타입은 객체 타입입니다. 이는 속성을 가진 모든 자바스크립트 값으로, 거의 모든 값이 해당됩니다! 객체 타입을 정의하려면 속성과 그 속성의 타입을 나열하기만 하면 됩니다.
예를 들어, 다음은 포인트와 같은 객체를 매개변수로 받는 함수입니다.
// 매개변수의 타입 주석은 객체 타입입니다
function printCoord(pt: { x: number; y: number }) {
console.log("좌표의 x 값은 " + pt.x);
console.log("좌표의 y 값은 " + pt.y);
}
printCoord({ x: 3, y: 7 });
여기서는 x
와 y
라는 두 속성을 가진 타입으로 매개변수를 주석 처리했으며, 두 속성 모두 숫자 타입입니다. 속성을 구분하기 위해 ,
나 ;
를 사용할 수 있으며, 마지막 구분자는 선택적입니다.
각 속성의 타입 부분도 선택 사항입니다. 타입을 지정하지 않으면 any
로 가정됩니다.
선택적 속성
객체 타입은 일부 또는 모든 속성이 선택적임을 명시할 수 있습니다. 이를 위해 속성 이름 뒤에 ?
를 추가하세요.
function printName(obj: { first: string; last?: string }) {
// ...
}
// 둘 다 가능
printName({ first: "Bob" });
printName({ first: "Alice", last: "Alisson" });
자바스크립트에서 존재하지 않는 속성에 접근하면 런타임 오류 대신 undefined
값을 받습니다. 이 때문에 선택적 속성을 읽을 때는 사용하기 전에 undefined
인지 확인해야 합니다.
function printName(obj: { first: string; last?: string }) {
// 오류 - 'obj.last'가 제공되지 않았다면 충돌할 수 있음!
console.log(obj.last.toUpperCase());
// 'obj.last'가 'undefined'일 수 있음
if (obj.last !== undefined) {
// 가능
console.log(obj.last.toUpperCase());
}
// 현대 자바스크립트 문법을 사용한 안전한 대안:
console.log(obj.last?.toUpperCase());
}
합집합 타입
타입스크립트의 타입 시스템은 다양한 연산자를 사용하여 기존 타입들로부터 새로운 타입들을 구성할 수 있게 합니다. 이제 몇 가지 타입을 작성하는 방법을 알게 되었으니, 이들을 흥미로운 방식으로 결합하기 시작할 시간입니다.
합집합 타입 정의하기
첫 번째로 볼 수 있는 타입 결합 방법은 합집합(Union) 타입입니다. 합집합 타입은 두 개 이상의 다른 타입들로부터 형성된 타입으로 그 값이 그 타입들 중 어느 하나일 수 있음을 나타냅니다. 이 타입들을 합집합의 멤버라고 합니다.
문자열이나 숫자에 작동할 수 있는 함수를 작성해 봅시다.
function printId(id: number | string) {
console.log("당신의 ID는: " + id);
}
// 가능
printId(101);
// 가능
printId("202");
// 오류
printId({ myID: 22342 });
// '{ myID: number; }' 타입은 'string | number' 타입의 매개변수에 할당할 수 없습니다.
합집합 타입 다루기
합집합 타입에 맞는 값을 제공하는 것은 간단합니다. 단순히 합집합의 멤버 중 하나에 해당하는 타입을 제공하면 됩니다. 합집합 타입의 값을 가지고 있다면, 그 값으로 어떻게 작업을 할까요?
타입스크립트는 합집합의 모든 멤버에 대해 유효한 연산만을 허용합니다. 예를 들어, string | number
합집합이 있다면, 문자열에만 사용할 수 있는 메서드를 사용할 수 없습니다.
function printId(id: number | string) {
console.log(id.toUpperCase());
// 'toUpperCase' 속성은 'string | number' 타입에 존재하지 않습니다.
// 'toUpperCase' 속성은 'number' 타입에 존재하지 않습니다.
}
해결책은 코드를 사용하여 합집합을 좁혀나가는 것입니다. 이는 타입 주석 없이 자바스크립트에서 할 때와 같은 방식입니다. 코드의 구조에 기반하여 타입스크립트가 값에 대해 더 구체적인 타입을 유추할 수 있을 때 좁혀짐(narrowing)이 발생합니다.
예를 들어, 타입스크립트는 typeof
값이 "string"
인 경우에만 문자열 값일 것임을 알고 있습니다.
function printId(id: number | string) {
if (typeof id === "string") {
// 이 분기에서 id는 'string' 타입입니다
console.log(id.toUpperCase());
} else {
// 여기에서 id는 'number' 타입입니다
console.log(id);
}
}
다른 예로 Array.isArray
와 같은 함수를 사용하는 경우입니다.
function welcomePeople(x: string[] | string) {
if (Array.isArray(x)) {
// 여기에서 'x'는 'string[]'입니다
console.log("Hello, " + x.join(" and "));
} else {
// 여기에서 'x'는 'string'입니다
console.log("Welcome lone traveler " + x);
}
}
else
분기에서 특별히 할 것은 없습니다. 만약 x
가 string[]
이 아니라면, 그것은 string
이었을 것입니다.
때때로 모든 멤버가 공통적으로 가지는 것이 있는 합집합을 가질 수 있습니다. 예를 들어, 배열과 문자열 모두 slice
메서드를 가지고 있습니다. 합집합의 모든 멤버가 공통 속성을 가지고 있다면, 좁혀지지 않아도 그 속성을 사용할 수 있습니다.
// 반환 타입은 'number[] | string'으로 추론됩니다
function getFirstThree(x: number[] | string) {
return x.slice(0, 3);
}
타입들의 합집합이 그 타입들의 속성들의 교집합처럼 보일 수 있습니다. 이는 우연이 아니며 합집합이라는 이름은 타입 이론에서 유래했습니다.
number | string
합집합은 각 타입에서 값들의 합집합을 취함으로써 구성됩니다. 두 세트가 각각에 대한 사실을 가지고 있을 때, 그 세트들 자체의 합집합에 적용되는 것은 그 사실들의 교집합뿐임을 알 수 있습니다. 예를 들어, 모자를 쓴 키 큰 사람들의 방과 스페인어를 하는 모자를 쓴 사람들의 방을 합친 후, 모든 사람에 대해 우리가 알 수 있는 것은 그들이 모자를 쓰고 있다는 것뿐입니다.
타입 별칭
지금까지 우리는 객체 타입과 합집합 타입을 타입 주석에 직접 작성함으로써 사용해왔습니다. 이는 편리하지만 동일한 타입을 여러 번 사용하고 싶고 하나의 이름으로 참조하고 싶은 경우가 흔합니다.
타입 별칭은 바로 그것입니다. 어떤 타입이든 이름을 지정할 수 있습니다. 타입 별칭의 문법은 다음과 같습니다.
type Point = {
x: number;
y: number;
};
// 앞선 예제와 정확히 동일
function printCoord(pt: Point) {
console.log("좌표의 x 값은 " + pt.x);
console.log("좌표의 y 값은 " + pt.y);
}
printCoord({ x: 100, y: 100 });
사실 타입 별칭은 객체 타입뿐만 아니라 어떤 타입에 대해서도 이름을 지정할 수 있습니다. 예를 들어, 타입 별칭은 합집합 타입에 이름을 지정할 수 있습니다.
type ID = number | string;
별칭은 단지 별칭일 뿐입니다. 타입 별칭을 사용해서 같은 타입의 다른/구별되는 "버전"을 만들 수는 없습니다. 별칭을 사용할 때는 별칭이 가리키는 타입을 직접 작성한 것처럼 정확히 동일합니다. 다시 말해, 이 코드는 불법처럼 보일 수 있지만 두 타입이 같은 타입에 대한 별칭이기 때문에 타입스크립트는 문제가 없다고 판단합니다.
type UserInputSanitizedString = string;
function sanitizeInput(str: string): UserInputSanitizedString {
return sanitize(str);
}
// 정제된 입력 생성
let userInput = sanitizeInput(getInput());
// 여전히 문자열로 재할당 가능
userInput = "새 입력";
인터페이스
인터페이스 선언은 객체 타입에 이름을 지정하는 또 다른 방법입니다.
interface Point {
x: number;
y: number;
}
function printCoord(pt: Point) {
console.log("좌표의 x 값은 " + pt.x);
console.log("좌표의 y 값은 " + pt.y);
}
printCoord({ x: 100, y: 100 });
위에서 타입 별칭을 사용했을 때와 마찬가지로, 예제는 익명 객체 타입을 사용한 것처럼 정확히 작동합니다. 타입스크립트는 printCoord에 전달된 값의 구조에만 관심이 있습니다. 타입스크립트가 타입의 구조와 기능에만 관심을 가지는 이유를 구조적 타입 시스템이라고 부릅니다.
타입 별칭과 인터페이스의 차이점
타입 별칭과 인터페이스는 매우 유사하며, 많은 경우에 자유롭게 선택할 수 있습니다. 인터페이스의 거의 모든 기능은 타입에서 사용할 수 있으며, 주요 차이점은 타입은 새로운 속성을 추가하여 확장할 수 없지만 인터페이스는 항상 확장 가능하다는 것입니다.
인터페이스 vs 타입
인터페이스 확장
interface Animal {
name: string;
}
interface Bear extends Animal {
honey: boolean;
}
const bear = getBear();
bear.name;
bear.honey;
타입을 통한 교차로 확장
type Animal = {
name: string;
}
type Bear = Animal & {
honey: boolean;
}
const bear = getBear();
bear.name;
bear.honey;
기존 인터페이스에 새 필드 추가
interface Window {
title: string;
}
interface Window {
ts: TypeScriptAPI;
}
const src = 'const a = "Hello World"';
window.ts.transpileModule(src, {});
생성된 후 타입 변경 불가
type Window = {
title: string;
}
type Window = {
ts: TypeScriptAPI;
}
// 오류: 식별자 'Window'가 중복되었습니다.
이 개념들에 대해 뒤의 장에서 더 배우게 되므로, 바로 모든 것을 이해하지 못해도 걱정하지 마세요.
- 타입스크립트 버전 4.2 이전에는, 오류 메시지에 타입 별칭 이름이 익명 타입 대신 나타날 수 있습니다(원하는 경우와 원하지 않는 경우 모두). 인터페이스는 항상 오류 메시지에 이름과 함께 나타납니다.
- 타입 별칭은 선언 병합에 참여할 수 없지만, 인터페이스는 가능합니다.
- 인터페이스는 객체의 형태를 선언하는 데만 사용될 수 있으며, 원시 타입의 이름을 변경할 수는 없습니다.
- 인터페이스 이름은 오류 메시지에 항상 원래의 형태로 나타나지만, 이름으로 사용될 때만 그렇습니다.
대부분의 경우, 개인적인 선호에 따라 선택할 수 있으며, 타입스크립트가 다른 종류의 선언이 필요한 경우 알려줍니다. 휴리스틱이 필요하다면, 타입에서 사용할 수 있는 기능이 필요할 때까지 인터페이스를 사용하세요.
타입 단언(Assertion)
가끔 타입스크립트가 알 수 없는 값의 타입에 대한 정보를 알고 있을 수 있습니다.
예를 들어, document.getElementById
를 사용할 때 타입스크립트는 이것이 어떤 종류의 HTMLElement
를 반환할 것임만 알고 있지만, 당신은 페이지가 주어진 ID로 HTMLCanvasElement
를 항상 가지고 있을 것임을 알 수 있습니다.
이러한 상황에서 더 구체적인 타입을 명시하기 위해 타입 단언(Assertion)을 사용할 수 있습니다.
const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;
타입 주석처럼, 타입 단언도 컴파일러에 의해 제거되며 코드의 런타임 동작에 영향을 주지 않습니다.
각괄호 문법도 사용할 수 있습니다(단, 코드가 .tsx 파일 안에 있지 않은 경우에 한함), 이는 동등합니다.
const myCanvas = <HTMLCanvasElement>document.getElementById("main_canvas");
기억하세요: 타입 단언은 컴파일 타임에 제거되기 때문에, 타입 단언과 관련된 런타임 검사가 없습니다. 타입 단언이 잘못되었더라도 예외나
null
이 생성되지 않습니다.
타입스크립트는 타입을 더 구체적이거나 덜 구체적인 버전으로 변환하는 타입 단언만을 허용합니다. 이 규칙은 다음과 같은 "불가능한" 강제 변환을 방지합니다.
const x = "hello" as number;
// 'string' 타입을 'number' 타입으로 변환하는 것은 두 타입이 \
// 충분히 겹치지 않기 때문에 오류일 수 있습니다. 의도적이었다면 표현식을 'unknown'으로 먼저 변환하세요.
가끔 이 규칙은 너무 보수적이어서 유효할 수 있는 더 복잡한 강제 변환을 허용하지 않습니다. 이런 경우, 먼저 any
(또는 나중에 소개할 unknown
)로의 단언을 사용한 다음 원하는 타입으로의 단언을 사용할 수 있습니다.
const a = expr as any as T;
리터럴 타입
일반적인 string
과 number
타입 외에도 타입 위치에서 구체적인 문자열과 숫자를 참조할 수 있습니다.
이를 이해하는 한 가지 방법은 자바스크립트가 변수를 선언하는 다양한 방법을 제공한다는 점을 고려하는 것입니다. var
과 let
은 변수 내에 보관된 것을 변경할 수 있게 하고, const
는 그렇지 않습니다. 이는 타입스크립트가 리터럴에 대한 타입을 생성하는 방식에서 반영됩니다.
let changingString = "Hello World";
changingString = "Olá Mundo";
// `changingString`은 모든 가능한 문자열을 대표할 수 있으므로,
// 타입스크립트는 타입 시스템에서 그렇게 설명합니다
let changingString: string
const constantString = "Hello World";
// `constantString`은 오직 하나의 가능한 문자열만을 대표할 수 있으므로,
// 리터럴 타입 표현을 가집니다
const constantString: "Hello World"
자체적으로 리터럴 타입은 큰 가치가 없습니다.
let x: "hello" = "hello";
// OK
x = "hello";
// ...
x = "howdy";
// 타입 '"howdy"'는 타입 '"hello"'에 할당할 수 없습니다.
하나의 값만 가질 수 있는 변수는 큰 쓸모가 없습니다!
그러나 리터럴을 합집합으로 결합하면, 특정한 알려진 값 세트만을 받아들이는 함수와 같은 훨씬 유용한 개념을 표현할 수 있습니다.
function printText(s: string, alignment: "left" | "right" | "center") {
// ...
}
printText("Hello, world", "left");
printText("G'day, mate", "centre");
// 타입 '"centre"'는 타입 '"left" | "right" | "center"'의 매개변수에 할당할 수 없습니다.
숫자 리터럴 타입도 같은 방식으로 작동합니다.
function compare(a: string, b: string): -1 | 0 | 1 {
return a === b ? 0 : a > b ? 1 : -1;
}
물론, 리터럴 타입을 비리터럴 타입과 결합할 수 있습니다.
interface Options {
width: number;
}
function configure(x: Options | "auto") {
// ...
}
configure({ width: 100 });
configure("auto");
configure("automatic");
// 타입 '"automatic"'은 'Options | "auto"' 타입의 매개변수에 할당할 수 없습니다.
리터럴 타입에는 불리언 리터럴 타입이 하나 더 있습니다. 불리언 리터럴 타입은 단 두 가지, true
와 false
입니다. 실제로 boolean
타입 자체는 true | false
합집합에 대한 별칭일 뿐입니다.
리터럴 추론
변수를 객체로 초기화할 때 타입스크립트는 해당 객체의 속성 값이 나중에 변경될 수 있다고 가정합니다. 예를 들어, 다음과 같은 코드를 작성했다고 가정해 보겠습니다.
const obj = { counter: 0 };
if (someCondition) {
obj.counter = 1;
}
타입스크립트는 이전에 0이었던 필드에 1을 할당하는 것이 오류라고 가정하지 않습니다. 다시 말해, obj.counter
는 타입이 숫자이어야 하지 0이 아니어야 한다는 것입니다. 왜냐하면 타입은 읽기와 쓰기 동작 모두를 결정하는 데 사용되기 때문입니다.
문자열에 대해서도 마찬가지입니다.
declare function handleRequest(url: string, method: "GET" | "POST"): void;
const req = { url: "https://example.com", method: "GET" };
handleRequest(req.url, req.method);
// 'string' 타입은 '"GET" | "POST"' 타입의 매개변수에 할당할 수 없습니다.
위 예제에서 req.method
는 "GET"이 아닌 문자열로 추론됩니다. req
의 생성과 handleRequest
의 호출 사이에 코드가 평가될 수 있으며, 이 과정에서 req.method
에 "GUESS"와 같은 새 문자열을 할당할 수 있기 때문에, 타입스크립트는 이 코드를 오류로 간주합니다.
이를 해결하는 두 가지 방법이 있습니다.
- 다음 위치에 타입 단언을 추가함으로써 추론을 변경할 수 있습니다.
// 변경 1:
const req = { url: "https://example.com", method: "GET" as "GET" };
// 변경 2
handleRequest(req.url, req.method as "GET");
변경 1은 "req.method
가 항상 리터럴 타입 'GET'을 가지길 원한다"는 의미이며, 이후에 해당 필드에 "GUESS"를 할당할 가능성을 방지합니다. 변경 2는 "req.method
가 'GET' 값임을 다른 이유로 알고 있다"는 의미입니다.
- 전체 객체를 리터럴 타입으로 변환하려면
as const
를 사용할 수 있습니다.
const req = { url: "https://example.com", method: "GET" } as const;
handleRequest(req.url, req.method);
as const
접미사는 const와 같지만 타입 시스템에 대해 동작하며, 모든 속성이 문자열이나 숫자와 같은 보다 일반적인 버전 대신 리터럴 타입으로 할당되도록 보장합니다.
null과 undefined
자바스크립트에는 값이 없거나 초기화되지 않았음을 나타내는 두 가지 원시 값인 null
과 undefined
가 있습니다.
타입스크립트에도 같은 이름의 두 가지 타입이 있습니다. 이 타입들이 어떻게 동작하는지는 strictNullChecks
옵션을 켰는지에 따라 달라집니다.
strictNullChecks off
strictNullChecks
가 꺼져 있으면, null
이나 undefined
일 수 있는 값도 정상적으로 접근할 수 있으며, null
과 undefined
는 모든 타입의 속성에 할당할 수 있습니다. 이는 널 검사가 없는 언어(C#, 자바 등)의 동작과 유사합니다. 이러한 값들에 대한 검사가 없는 것은 일반적으로 버그의 주요 원인이므로, 코드베이스에서 실현 가능한 경우 항상 strictNullChecks
를 켜는 것이 좋습니다.
strictNullChecks on
strictNullChecks
가 켜져 있을 때, 값이 null
이나 undefined
인 경우, 해당 값의 메서드나 속성을 사용하기 전에 이러한 값들을 테스트해야 합니다. 선택적 속성을 사용하기 전에 undefined
를 검사하는 것처럼, null
일 수 있는 값을 검사하기 위해 좁혀짐을 사용할 수 있습니다.
function doSomething(x: string | null) {
if (x === null) {
// 아무것도 하지 않음
} else {
console.log("Hello, " + x.toUpperCase());
}
}
!
)
널-아님 단언 연산자 (후위 타입스크립트는 명시적인 검사 없이 타입에서 null
과 undefined
를 제거하기 위한 특별한 문법도 가지고 있습니다. 어떤 표현식 뒤에 !
를 쓰는 것은 그 값이 null
이나 undefined
가 아니라는 타입 단언과 효과적으로 동일합니다.
function liveDangerously(x?: number | null) {
// 오류 없음
console.log(x!.toFixed());
}
다른 타입 단언처럼, 이것은 코드의 런타임 동작을 변경하지 않으므로, 값이 null
이나 undefined
가 아님을 알고 있을 때만 !
를 사용하는 것이 중요합니다.
열거형(Enums)
열거형은 타입스크립트가 자바스크립트에 추가한 기능으로, 가능한 명명된 상수 집합 중 하나가 될 수 있는 값을 설명하는 데 사용됩니다. 대부분의 타입스크립트 기능과 달리, 이는 자바스크립트에 대한 타입 수준의 추가가 아니라 언어와 런타임에 추가된 것입니다. 이 때문에, 열거형은 존재한다는 것을 알아야 하지만, 확신이 들지 않는 한 사용을 미루는 것이 좋습니다. 열거형에 대해 더 자세히 알고 싶다면 열거형 참조 페이지에서 확인할 수 있습니다.
덜 흔한 원시 타입
타입 시스템에서 표현되는 자바스크립트의 나머지 원시 타입들을 언급할 가치가 있습니다. 여기서는 깊이 있게 다루지 않겠습니다.
bigint
ES2020부터, 자바스크립트에는 매우 큰 정수를 위한 원시 타입인 BigInt
가 있습니다.
// BigInt 함수를 통해 bigint 생성
const oneHundred: bigint = BigInt(100);
// 리터럴 구문을 통해 BigInt 생성
const anotherHundred: bigint = 100n;
BigInt에 대해 더 알아보려면 타입스크립트 3.2 릴리즈 노트를 참조하세요.
symbol
자바스크립트에는 함수 Symbol()
을 통해 전역적으로 고유한 참조를 생성하는 원시 타입이 있습니다.
const firstName = Symbol("name");
const secondName = Symbol("name");
if (firstName === secondName) {
// 절대 일어날 수 없음
}
이 비교는 'typeof firstName'과 'typeof secondName' 타입에 겹치는 부분이 없기 때문에 의도하지 않은 것으로 보입니다. 심볼에 대해 더 알아보려면 심볼 참조 페이지를 확인하세요.