제가 타입스크립트(TypeScript)를 강력 추천하는 이유
대략적으로, 타입스크립트(TypeScript)는 자바스크립트(JavaScript)에 타입(Type) 정보를 더한 것이라고 볼 수 있는데요.
이 추가된 타입 정보는 타입스크립트(TypeScript) 코드가 자바스크립트 엔진(JavaScript Engine)에 의해 실행되기 전에 제거됩니다.
그렇기 때문에 타입스크립트(TypeScript)를 작성하고 배포하는 것은 순수한 자바스크립트(JavaScript)보다 조금 더 손이 가는 일이긴 한데요.
'과연 이렇게 추가적인 노력이 들 만큼 가치가 있을까?' 하고 의문이 드실 수 있습니다.
이번 글에서 저는 '네, 그럴 가치가 있습니다!'라고 힘주어 말씀드리려고 하는데요.
타입스크립트(TypeScript)에 대해 아직은 반신반의하지만, 한번 알아볼 마음이 있으시다면 이 글을 꼭 읽어보시면 좋겠습니다.
이 글에서 사용하는 표기법 안내
타입스크립트(TypeScript) 코드 예제에서, 타입스크립트(TypeScript)가 알려주는 오류는 @ts-expect-error
로 시작하는 주석으로 보여드릴 건데요.
예를 들면 이렇습니다.
// @ts-expect-error: 산술 연산의 오른쪽에는 'any', 'number', 'bigint'
// 또는 열거형 타입이어야 합니다.
const value = 5 * '8';
이렇게 하면 이 글에 있는 모든 소스 코드를 자동으로 테스트하기가 더 쉬워지는데요.
또한, 이 @ts-expect-error
는 (자주 쓰이지는 않지만) 타입스크립트(TypeScript)에 내장된 유용한 기능이기도 합니다.
1. 타입스크립트(TypeScript)의 장점: 똑똑한 자동 완성 & 편집 중 더 많은 오류 발견!
타입스크립트(TypeScript)가 우리를 어떻게 도와주는지, 코드 예제를 통해 직접 확인해볼까요?
자동 완성과 오류 발견 측면에서 말이죠.
첫 번째 예제는 간단하지만, 뒤로 갈수록 좀 더 흥미로운 예제들이 기다리고 있습니다.
예제: 오타, 잘못된 타입, 누락된 인자
class Point {
x: number;
y: number;
constructor(x: number, y = x) {
this.x = x;
this.y = y;
}
}
const point1 = new Point(3, 8);
// @ts-expect-error: 'Point' 타입에 'z' 속성이 없습니다.
console.log(point1.z); // (A)
// @ts-expect-error: 'number' 타입에 'toUpperCase' 속성이 없습니다.
point1.x.toUpperCase(); // (B)
const point2 = new Point(3); // (C)
// @ts-expect-error: 1-2개의 인수가 필요한데, 0개를 받았습니다.
const point3 = new Point(); // (D)
// @ts-expect-error: 'string' 타입의 인수는 'number' 타입의
// 매개변수에 할당될 수 없습니다.
const point4 = new Point(3, '8'); // (E)
무슨 일이 일어나고 있는 걸까요?
- (A)줄: 타입스크립트(TypeScript)는
point1
이Point
타입이라는 것을 알고 있습니다. 그리고Point
타입에는.z
라는 속성(Property)이 없다는 것도 알기에, "이런 속성은 없어!"라고 바로 알려주는 건데요. - (B)줄:
point1.x
는 숫자(number
) 타입입니다. 숫자에는 문자열(String)을 대문자로 바꾸는.toUpperCase()
메소드(Method)가 없죠. 타입스크립트(TypeScript)는 이것도 놓치지 않습니다. - (C)줄:
new Point(3)
은 문제가 없습니다. 왜냐하면Point
의 생성자(Constructor)에서 두 번째 인자y
는 기본값(y = x
)이 있어서 생략이 가능하기 때문인데요. - (D)줄: 하지만
new Point()
처럼 아무 인자도 주지 않으면 오류가 납니다. 최소한 첫 번째 인자x
는 꼭 필요하기 때문입니다. - (E)줄:
new Point(3, '8')
처럼 두 번째 인자로 문자열 '8'을 넣으려고 하면, "두 번째 인자는 숫자(number
)여야 해!"라고 타입스크립트(TypeScript)가 막아줍니다.
(A)줄에서는 똑똑한 자동 완성 기능도 경험할 수 있습니다.
point1.
을 입력하는 순간, Point
가 가진 x
와 y
속성만 딱 보여주죠.
예제: 함수 결과 잘못 만들기
아래 자바스크립트(JavaScript) 코드에서 혹시 몇 가지 문제점을 발견하셨나요?
function reverseString(str) {
if (str.length === 0) {
return str;
}
Array.from(str).reverse();
}
이 코드에 타입 정보(Type Annotation)를 추가하면 (A)줄처럼) 타입스크립트(TypeScript)가 무엇을 알려주는지 봅시다.
// @ts-expect-error: 함수에 마지막 return 문이 없고, 반환 타입에
// 'undefined'가 포함되지 않습니다.
function reverseString(str: string): string { // (A)
if (str.length === 0) {
return str;
}
Array.from(str).reverse(); // (B)
}
타입스크립트(TypeScript)가 두 가지를 지적하는데요.
- 함수 끝에
return
문이 없다는 겁니다. 정말로 (B)줄 앞에return
을 쓰는 걸 깜빡해서, (B)줄 다음에는 암묵적으로undefined
가 반환되고 있었습니다. - 이렇게 암묵적으로 반환된
undefined
는 (A)줄에서 약속한 반환 타입인string
(문자열)과 맞지 않다는 겁니다.
이 문제를 고치고 나면, 타입스크립트(TypeScript)는 또 다른 오류를 지적합니다.
function reverseString(str: string): string { // (A)
if (str.length === 0) {
return str;
}
// @ts-expect-error: 'string[]' 타입은 'string' 타입에
// 할당될 수 없습니다.
return Array.from(str).reverse(); // (B)
}
(B)줄에서 Array.from(str).reverse()
는 문자열을 배열로 바꾼 뒤 뒤집었으니, 그 결과는 배열(string[]
)인데요.
하지만 (A)줄에서 우리는 문자열(string
)을 반환하기로 약속했습니다.
타입스크립트(TypeScript)는 "배열을 반환하면 안 돼!"라고 알려주는 거죠.
이 문제까지 해결하면, 드디어 타입스크립트(TypeScript)는 우리 코드를 만족스러워합니다.
function reverseString(str: string): string {
if (str.length === 0) {
return str;
}
return Array.from(str).reverse().join('');
}
예제: 선택적 속성(Optional Properties) 다루기
다음 예제에서는 이름 정보를 객체(Object)로 다뤄볼 건데요.
이 객체의 구조는 아래와 같은 타입스크립트(TypeScript) 타입(Type)으로 정의합니다.
type NameDef = {
name?: string, // (A)
nick?: string, // (B)
};
즉, NameDef
객체는 name
과 nick
이라는 두 개의 속성(Property)을 가질 수 있고, 그 값은 문자열(String)입니다.
그런데 속성 이름 뒤에 붙은 물음표(?
) 보이시나요?
(A)와 (B)줄의 이 물음표는 이 속성들이 '선택적(Optional)'이라는 뜻입니다.
즉, 이 속성들이 객체에 없을 수도 있다는 거죠.
아래 코드는 이 NameDef
를 사용하는 함수인데, 오류를 포함하고 있어서 타입스크립트(TypeScript)가 경고를 보냅니다.
function getName(nameDef: NameDef): string {
// @ts-expect-error: 'string | undefined' 타입은 'string' 타입에
// 할당될 수 없습니다.
return nameDef.nick ?? nameDef.name;
}
??
는 널 병합 연산자(nullish coalescing operator)라고 부르는데요, 왼쪽 값이 null
이나 undefined
가 아니면 왼쪽 값을 그대로 쓰고, 만약 null
이나 undefined
면 오른쪽 값을 쓰는 친구입니다.
문제는 nameDef.nick
과 nameDef.name
둘 다 선택적 속성이라서 값이 없을 수 있다는 건데요.
만약 둘 다 없으면, 이 함수의 결과는 undefined
가 될 수 있습니다.
하지만 우리는 함수가 항상 문자열(string
)을 반환한다고 약속했죠.
타입스크립트(TypeScript)는 "결과가 undefined
일 수도 있는데, 문자열만 반환한다고 하면 안 돼!"라고 지적하는 겁니다.
이 문제를 해결하면, 타입스크립트(TypeScript)는 더 이상 오류를 보고하지 않습니다.
function getName(nameDef: NameDef): string {
return nameDef.nick ?? nameDef.name ?? '(Anonymous)';
}
이제 nameDef.nick
도 없고 nameDef.name
도 없으면, '(Anonymous)'라는 문자열을 반환하게 되어 항상 문자열을 반환한다는 약속을 지킬 수 있게 됩니다.
예제: switch
케이스(Case) 빼먹었을 때
색깔을 나타내는 다음 타입을 생각해봅시다.
type Color = 'red' | 'green' | 'blue';
즉, Color
타입의 값은 'red', 'green', 'blue'라는 세 가지 문자열 중 하나여야 합니다.
다음 함수는 이런 색깔 이름을 받아서 CSS에서 사용하는 16진수 색상 값(예: #FF0000)으로 바꿔주는 일을 하는데요.
function getCssColor(color: Color): `#${string}` {
switch (color) {
case 'red':
return '#FF0000';
case 'green':
// @ts-expect-error: '"00FF00"' 타입은 '`#${string}`' 타입에
// 할당될 수 없습니다.
return '00FF00'; // (A)
default:
// (B)
// @ts-expect-error: '"blue"' 타입의 인수는 'never' 타입의
// 매개변수에 할당될 수 없습니다.
throw new UnexpectedValueError(color); // (C)
}
}
(A)줄에서는 오류가 발생합니다.
왜냐하면 우리는 #
기호로 시작하는 문자열(#${string}
)을 반환하기로 약속했는데, '00FF00'에는 #
이 빠져있기 때문인데요.
(C)줄의 오류는 우리가 처리해야 할 케이스, 즉 'blue'를 빼먹었다는 것을 의미합니다.
이 오류 메시지를 이해하려면, 타입스크립트(TypeScript)가 switch
문을 거치면서 color
의 타입을 계속 좁혀나간다는 것을 알아야 하는데요.
switch
문 이전에는color
의 타입이'red' | 'green' | 'blue'
였습니다.- 'red'와 'green' 케이스를 지나고 나면, (B) 지점에서
color
의 타입은 이제 'blue'만 남게 됩니다. - 그런데
UnexpectedValueError
의 매개변수 타입인never
는 '절대 도달해서는 안 되는' 코드 영역의 변수에 사용되는 특별한 타입입니다. 즉, 'blue'라는 값이 여전히 남아있다는 것은, 우리가 처리하지 않은 케이스가 있다는 신호인 셈이죠. (이never
타입에 대해 더 궁금하시면 2ality 블로그의 "타입스크립트의 바닥 타입 never" 글을 참고해 보세요.)
두 오류를 모두 수정하면, 코드는 이렇게 됩니다.
function getCssColor(color: Color): `#${string}` {
switch (color) {
case 'red':
return '#FF0000';
case 'green':
return '#00FF00';
case 'blue':
return '#0000FF';
default:
throw new UnexpectedValueError(color);
}
}
참고로, UnexpectedValueError
라는 오류 클래스는 이렇게 생겼습니다.
class UnexpectedValueError extends Error {
constructor(
// 이 매개변수의 타입을 never로 지정해서 타입 검사를 가능하게 함
value: never,
// value가 프로토타입 없는 객체거나 심볼일 때 예외 피하기
message = `Unexpected value: ${{}.toString.call(value)}`
) {
super(message)
}
}
마지막으로, 타입스크립트(TypeScript)는 getCssColor
함수의 인자에 대해 자동 완성 기능도 제공합니다.
'red', 'green', 'blue'만 딱 보여주죠.
예제: 코드가 일부 경우를 잘못 처리할 때
다음 타입은 콘텐츠(Content)를 객체로 설명합니다.
콘텐츠는 텍스트, 이미지, 비디오 중 하나일 수 있는데요.
type Content =
| {
kind: 'text',
charCount: number,
}
| {
kind: 'image',
width: number,
height: number,
}
| {
kind: 'video',
width: number,
height: number,
runningTimeInSeconds: number,
}
;
아래 코드에서는 이 Content
타입을 잘못 사용하고 있습니다.
function getWidth(content: Content): number {
// @ts-expect-error: 'Content' 타입에 'width' 속성이 없습니다.
return content.width;
}
타입스크립트(TypeScript)가 경고하는 이유는, 모든 종류의 Content
가 .width
속성을 가지고 있지는 않기 때문입니다.
텍스트(text
) 콘텐츠에는 .width
가 없죠.
하지만 모든 Content
객체는 어떤 종류인지 알려주는 .kind
속성을 가지고 있는데요.
이걸 이용해서 오류를 수정할 수 있습니다.
function getWidth(content: Content): number {
if (content.kind === 'text') {
return NaN;
}
return content.width; // (A)
}
(A)줄에서 타입스크립트(TypeScript)가 더 이상 불평하지 않는 것을 주목하세요.
왜냐하면 if (content.kind === 'text')
조건문을 통해, .width
속성이 없는 텍스트 콘텐츠의 경우를 이미 제외했기 때문입니다.
이 조건문 안에서는 content
가 이미지나 비디오라는 것을 타입스크립트(TypeScript)가 알아채고, .width
속성에 안전하게 접근할 수 있다고 판단하는 거죠.
2. 타입스크립트(TypeScript)의 장점: 함수 매개변수와 결과에 대한 타입 표기는 훌륭한 문서화!
다음과 같은 자바스크립트(JavaScript) 코드를 봅시다.
function filter(items, callback) {
// ···
}
이것만 봐서는 filter
함수가 어떤 인자(Argument)들을 기대하는지, 그리고 무엇을 반환하는지 명확히 알기 어렵습니다.
반면에, 이 함수에 해당하는 타입스크립트(TypeScript) 코드는 이렇게 보이는데요.
function filter(
items: Iterable<string>,
callback: (item: string, index: number) => boolean
): Iterable<string> {
// ···
}
이 타입 정보는 우리에게 많은 것을 알려줍니다.
items
인자는 문자열(String)들을 순회할 수 있는 무언가(Iterable<string>
)여야 합니다.callback
함수는 문자열(item
)과 숫자(index
)를 받아서, 불리언(Boolean,true
또는false
) 값을 반환해야 합니다.filter
함수의 결과는 또 다른 문자열들을 순회할 수 있는 무언가(Iterable<string>
)입니다.
네, 이런 타입 표기법이 처음에는 좀 낯설 수 있습니다.
하지만 한번 익숙해지고 나면, 이 함수가 대략 어떤 일을 하는지 영어로 된 긴 설명을 읽는 것보다 훨씬 빠르게 파악할 수 있습니다.
(물론, 타입 표기법과 함수 이름만으로는 알 수 없는 세부 사항을 채우기 위해 여전히 설명 글이 필요하긴 합니다.)
저는 개인적으로 자바스크립트(JavaScript) 코드 베이스보다 타입스크립트(TypeScript) 코드 베이스를 이해하기가 더 쉬운데요.
왜냐하면 타입스크립트(TypeScript)는 코드 자체에 또 다른 층의 문서화를 제공하기 때문입니다.
이 추가적인 문서화는 팀으로 일할 때 특히 빛을 발하는데요.
코드를 어떻게 사용해야 하는지가 더 명확해지고, 혹시 잘못 사용하려고 하면 타입스크립트(TypeScript)가 미리 경고해주기 때문입니다.
제가 자바스크립트(JavaScript) 코드를 타입스크립트(TypeScript)로 옮길 때마다 흥미로운 현상을 발견하는데요.
어떤 함수나 메소드의 매개변수에 적절한 타입을 찾아주려면, 그 함수가 어디서 어떻게 호출되는지를 확인해야 한다는 겁니다.
즉, 정적 타입(Static Type) 정보 덕분에, 다른 곳을 찾아봐야 알 수 있는 정보가 코드 바로 그 자리에 있게 되는 거죠.
3. 타입스크립트(TypeScript)의 장점: 더 안전하고 쉬운 리팩토링(Refactoring)!
리팩토링(Refactoring)은 많은 통합 개발 환경(IDE, Integrated Development Environment)에서 제공하는, 코드의 기능을 바꾸지 않으면서 구조를 개선하는 자동화된 코드 변환 작업을 말합니다.
예를 들어 '메소드 이름 바꾸기'도 리팩토링의 한 종류인데요.
순수한 자바스크립트(JavaScript)에서는 같은 이름이 서로 다른 메소드를 가리킬 수도 있어서 이 작업이 꽤 까다로울 수 있습니다.
하지만 타입스크립트(TypeScript)는 메소드와 타입들이 어떻게 연결되어 있는지에 대한 더 많은 정보를 가지고 있기 때문에, 메소드 이름을 바꾸는 작업을 훨씬 안전하게 할 수 있습니다.
4. 타입스크립트(TypeScript) 사용, 예전보다 훨씬 쉬워졌습니다!
이제는 자바스크립트(JavaScript)와 비교했을 때, 타입스크립트(TypeScript)를 사용하기 위해 꼭 추가적인 빌드(Build) 단계를 거칠 필요가 없는 경우가 많아졌습니다.
- 노드제이에스(Node.js), 데노(Deno), 번(Bun) 같은 서버 측 자바스크립트(JavaScript) 플랫폼에서는 타입스크립트(TypeScript)를 별도의 컴파일 없이 바로 실행할 수 있습니다.
- 바이트(Vite)와 같은 대부분의 번들러(Bundler)들은 타입스크립트(TypeScript)를 기본적으로 지원합니다.
더 좋은 소식도 있는데요.
- 타입스크립트(TypeScript)를 자바스크립트(JavaScript)로 컴파일하는 것이 '타입 제거(Type Stripping)'라는 기법 덕분에 훨씬 효율적으로 변했습니다. 이 기법은 타입스크립트(TypeScript) 문법의 타입 부분만 간단히 제거하고 다른 변환은 하지 않아 매우 빠릅니다(더 자세한 정보).
- 패키지(Package)를 만드는 것도 개선되었습니다.
- npm: 라이브러리(Library)가 아닌 앱 패키지(Package)는 타입스크립트(TypeScript)로 게시할 수 있습니다. 라이브러리 패키지(Library Package)는 여전히 자바스크립트(JavaScript) 코드와 타입 정보를 담은 선언 파일(.d.ts)을 포함해야 하지만, 이 선언 파일을 생성하는 것 또한 '고립된 선언(Isolated Declarations)' 기법 덕분에 개선되었습니다.
- JSR(자바스크립트 레지스트리, JavaScript Registry): npm의 대안으로, 패키지를 타입스크립트(TypeScript) 파일 그대로 업로드할 수 있는 곳입니다. 다양한 플랫폼을 지원하며, 노드제이에스(Node.js)를 위해서는 자동으로 자바스크립트(JavaScript) 파일과 선언 파일을 생성해줍니다.
아쉽게도, 타입 검사(Type Checking) 자체는 여전히 상대적으로 느리고, 타입스크립트 컴파일러(TypeScript Compiler) tsc
를 통해 수행해야 합니다.
5. 타입스크립트(TypeScript) 사용의 단점
물론 타입스크립트(TypeScript)에도 고려할 점들이 있습니다.
- 자바스크립트(JavaScript) 위에 추가되는 계층이기 때문에, 아무래도 복잡성이 더해지고 배워야 할 것들이 늘어납니다.
- npm 패키지(Package)를 사용하려면 정적 타입 정의(.d.ts 파일)가 있어야 하는데요. 요즘 대부분의 패키지는 자체적으로 타입 정의를 가지고 있거나, 데피니틀리타입트(DefinitelyTyped)라는 곳에서 타입 정의를 구할 수 있습니다. 하지만 특히 후자의 경우, 가끔 타입 정의가 실제와 약간 다르거나 부정확할 수 있어서, 정적 타이핑(Static Typing)이 없었다면 겪지 않았을 문제를 일으키기도 합니다.
tsconfig.json
파일을 통해 타입스크립트(TypeScript) 설정을 하는 것도 약간의 복잡성을 더하고, 이 설정에 따라 코드 베이스의 타입 검사 방식이 매우 다양해질 수 있다는 점이 있는데요. 하지만 이 부분에 대해 두 가지 완화 요인이 있습니다.- 제 개인 프로젝트들의 경우, 이제는 최대한 엄격한(Strict)
tsconfig.json
설정을 사용하는데요. 이렇게 하니 '내tsconfig.json
이 어떻게 보여야 할까?'에 대한 고민이 사라졌습니다. - 앞서 언급한 '타입 제거(Type Stripping)'는 제게
tsconfig.json
의 역할을 명확히 해주었습니다. 즉,tsconfig.json
은 이제 주로 '타입 검사'가 어떻게 작동할지만 설정하고, 자바스크립트(JavaScript) 코드를 생성하는 것은tsconfig.json
없이도 할 수 있게 된 거죠.
- 제 개인 프로젝트들의 경우, 이제는 최대한 엄격한(Strict)
6. 타입스크립트(TypeScript) 관련 자주 묻는 질문 (FAQ)
타입스크립트(TypeScript) 코드는 너무 무겁지 않나요?
타입스크립트(TypeScript) 코드가 무겁게 느껴질 수도 있습니다.
하지만 꼭 그럴 필요는 없는데요.
예를 들어, '타입 추론(Type Inference)' 덕분에 우리는 생각보다 적은 타입 표기(Type Annotation)만으로도 충분할 때가 많습니다.
function setDifference<T>(set1: Set<T>, set2: Set<T>): Set<T> {
const result = new Set<T>();
for (const elem of set1) {
if (!set2.has(elem)) {
result.add(elem);
}
}
return result;
}
이 코드에서 자바스크립트(JavaScript)가 아닌 문법은 <T>
뿐인데요.
첫 번째 setDifference<T>
는 setDifference
함수가 '타입 매개변수(Type Parameter)' <T>
를 가진다는 뜻입니다.
즉, 타입 레벨(Type Level)에서의 매개변수 같은 거죠.
그 뒤에 나오는 모든 <T>
는 이 타입 매개변수를 가리킵니다.
이것이 의미하는 바는 다음과 같습니다.
- 매개변수
set1
과set2
는 둘 다Set
객체인데, 그 안의 요소(Element)들이 모두 같은 타입T
를 가집니다. - 함수의 결과 또한
Set
객체이고, 그 안의 요소들도set1
,set2
와 같은 타입T
를 가집니다.
주목할 점은, 우리가 보통 이 함수를 사용할 때 <T>
가 무슨 타입인지 굳이 알려줄 필요가 없다는 건데요.
타입스크립트(TypeScript)가 함수에 전달된 인자(Argument)들의 타입을 보고 T
가 무엇인지 자동으로 알아냅니다.
assert.deepEqual(
setDifference(new Set(['a', 'b']), new Set(['b'])),
new Set(['a']),
);
assert.deepEqual(
setDifference(new Set(['a', 'b']), new Set(['a', 'b'])),
new Set(),
);
setDifference
함수를 사용하는 부분에서는, 이 경우 타입스크립트(TypeScript) 코드가 자바스크립트(JavaScript) 코드와 전혀 다르지 않습니다.
타입스크립트(TypeScript)는 자바스크립트(JavaScript)를 C#이나 자바(Java)처럼 만들려는 건가요?
시간이 흐르면서 타입스크립트(TypeScript)의 성격도 진화해왔습니다.
타입스크립트(TypeScript) 0.8 버전이 2012년 10월에 나왔을 때는 자바스크립트(JavaScript)가 오랫동안 정체되어 있던 시기였는데요.
그래서 타입스크립트(TypeScript)는 당시 자바스크립트(JavaScript)에 없다고 느껴졌던 기능들, 예를 들어 클래스(Class), 모듈(Module), 이넘(Enum) 등을 추가했습니다.
그 이후로 자바스크립트(JavaScript)는 많은 새로운 기능들을 갖추게 되었는데요.
이제 타입스크립트(TypeScript)는 자바스크립트(JavaScript)가 제공하는 기능을 따라가며, 더 이상 새로운 언어 수준의 기능을 자체적으로 만들지 않습니다.
예를 들면 다음과 같습니다.
- 2012년에 타입스크립트(TypeScript)는 자체적인 모듈 방식을 가지고 있었습니다. 하지만 지금은 자바스크립트(JavaScript) 표준인 ECMAScript 모듈(ECMAScript Module)과 CommonJS 모듈(CommonJS)을 모두 지원합니다.
- 2012년에 타입스크립트(TypeScript)는 함수로 변환되는 클래스(Class)를 가지고 있었습니다. 2015년에 ECMAScript 6(ES6)가 나온 이후, 타입스크립트(TypeScript)는 자바스크립트(JavaScript)에 내장된 클래스(Class)를 지원합니다.
- 2015년에 타입스크립트(TypeScript)는 앵귤러(Angular)를 지원하기 위해 자체적인 데코레이터(Decorator)를 도입했습니다. 2022년에 ECMAScript 데코레이터(ECMAScript Decorator)가 Stage 3(표준화 과정의 거의 마지막 단계)에 도달하자, 타입스크립트(TypeScript)는 이를 지원하기 시작했습니다. (더 자세한 정보는 2ality 블로그의 ECMAScript 데코레이터 글 중 "데코레이터의 역사" 섹션을 참고하세요.)
만약 타입 검사 옵션 중 erasableSyntaxOnly
를 활성화하면, 타입스크립트(TypeScript)는 자바스크립트(JavaScript)의 언어 기능만 지원하게 됩니다.
예를 들어, 이넘(Enum) 같은 타입스크립트(TypeScript) 고유 기능은 사용할 수 없게 되죠.
이 옵션은 앞서 말한 '타입 제거(Type Stripping)'를 가능하게 해주며, 타입스크립트(TypeScript) 개발자들 사이에서 꽤 인기가 있습니다.
따라서 미래에는 대부분의 타입스크립트(TypeScript)가 정말로 순수한 자바스크립트(JavaScript)에 타입(Type) 정보만 더한 형태가 될 것으로 보입니다.
타입스크립트(TypeScript)가 더 나은 이넘(Enum)이나 패턴 매칭(Pattern Matching) 같은 기능을 갖게 된다면, 그것은 자바스크립트(JavaScript)에 그 기능들이 먼저 추가되었을 때일 겁니다.
타입스크립트(TypeScript)는 객체 지향 프로그래밍(OOP)만을 위한 것이 아닙니다!
흔한 오해 중 하나는 타입스크립트(TypeScript)가 클래스(Class) 위주의 객체 지향 프로그래밍(OOP) 스타일만 지원한다는 것인데요.
타입스크립트(TypeScript)는 함수형 프로그래밍(Functional Programming) 패턴들도 아주 잘 지원합니다.
예를 들어 '구별된 유니온(Discriminated Union)'은 대수적 데이터 타입(Algebraic Data Types)의 (조금 덜 우아한) 버전이라고 볼 수 있는데요.
type Content =
| {
kind: 'text',
charCount: number,
}
| {
kind: 'image',
width: number,
height: number,
}
| {
kind: 'video',
width: number,
height: number,
runningTimeInSeconds: number,
}
;
이런 데이터 타입은 함수형 언어인 하스켈(Haskell)에서는 (간단하게 레이블 없이 표현하면) 이렇게 보일 겁니다.
data Content =
Text Int
| Image Int Int
| Video Int Int Int
더 자세한 정보는 타입스크립트 핸드북(TypeScript Handbook)의 "함수형 프로그래머를 위한 타입스크립트" 부분을 참고하시면 좋습니다.
고급 타입(Type) 사용법은 매우 복잡해 보이는데, 꼭 배워야 하나요?
타입스크립트(TypeScript)를 보통의 방식으로 사용할 때는 거의 항상 비교적 간단한 타입들만 다루게 됩니다.
라이브러리(Library)를 만들 때는 복잡한 타입이 유용할 수 있지만, 그런 경우에도 그 타입을 '작성하는 것'이 복잡한 것이지, '사용하는 것'이 복잡하지는 않습니다.
제 일반적인 권장 사항은 타입을 가능한 한 단순하게 만들어서 이해하고 유지보수하기 쉽게 하는 것인데요.
만약 코드에 대한 타입이 너무 복잡하다면, 코드를 변경하거나(예: 함수 하나 대신 두 개 사용), 타입으로 모든 세부 사항을 다 잡아내려고 하지 않음으로써 타입을 단순화할 수 있는 경우가 많습니다.
고급 타입을 이해하는 데 핵심적인 통찰 중 하나는, 그것들이 대부분 타입 레벨(Type Level)에서의 새로운 프로그래밍 언어와 같고, 주로 입력 타입(Input Type)이 어떻게 출력 타입(Output Type)으로 변환되는지를 기술한다는 점인데요.
여러 면에서 자바스크립트(JavaScript)와 유사합니다.
거기에는 다음과 같은 것들이 있습니다.
- 변수 (타입 변수, Type Variable)
- 매개변수가 있는 함수 (타입 매개변수가 있는 제네릭 타입, Generic Type)
- 조건 표현식
C ? T : F
(조건부 타입, Conditional Type) - 객체를 순회하는 루프 (맵드 타입, Mapped Type)
- 기타 등등
이 주제에 대해 더 궁금하시면, "Tackling TypeScript" 책의 "타입으로 계산하기 개요" 부분을 참고해 보세요.
복잡한 타입이 그만한 가치가 있나요?
때로는 그렇습니다.
예를 들어, 제가 실험 삼아 간단한 SQL API를 만들었는데, 이 API는 코드를 편집하는 동안 (오타 등을 쳤을 때) 많은 타입 자동 완성 기능과 경고를 제공합니다.
중요한 것은 이 API를 '작성하는 것'은 약간의 노력이 필요했지만, '사용하는 것'은 간단하다는 점인데요.
'Javascript' 카테고리의 다른 글
TypeScript 컴파일러, Go 언어로 갈아탄다고요?! 속사정 한번 알아볼까요? (0) | 2025.03.22 |
---|---|
TypeScript 객체 타입 Union과 Intersection (0) | 2025.03.22 |
타입스크립트(TypeScript)가 뭔가요? 자바스크립트(JavaScript) 개발자를 위한 간단 소개 (0) | 2025.03.22 |
타입스크립트(TypeScript) 고수처럼? 복잡한 타입도 '확실하게' 검사하는 비법 대공개! (0) | 2025.03.22 |
타입스크립트의 깜짝 비밀: 조건문은 어떻게 타입을 예측할까요? (0) | 2025.03.22 |