
any 스크립트가 되지 않기 위해...!
require VS import
type VS interface
any VS unknown
Record/enum/const enum/as const/satisfies
string Union|Tuple
type guards | assert
require/exports
런타임(실행 시점)에 모듈을 가져온다. 동적 불러오기가 가능함. if문을 내부에서 사용 가능.
Node.js 레거시 코드 이거나, tsconfig의 module이 commonjs일 때 사용함
비동기 불가능하고, 전부 한꺼번에 가져온다.
옜날에 주로 사용했던 방식이다.
import / export (ES Modules)
정적(컴파일 시점) 분석하거나, Tree Shaking 지원함. 동적 import 지원함
ES6 이상, module이 ESNext or ES6 설정 시에 추천함.
애초에 최적화 및 자동완성이 지원하며 타입 추론이 뛰어나다.
// CommonJS
const fs = require('fs');
// ESM
import fs from 'fs';
type
용도는 타입 별칭(유니온, 튜플, 교차 타입, 함수 타입 등 자유롭게 정의 가능)
확장은 불가능(&로 교차는 가능함)
재선언 불가능하며 복잡한 조합, 유니온 타입, 함수 시그니처 등 활용해야 할때 사용하면 좋다.
interface
객체, 클래스 설계 시 구조 정의에 최적화 가능함
extends 및 선언적 병합(자동 합침) 가능
재선언 가능(동일 이름으로 여러번 작성시에는 병합됨)
API 응답 정의, DTO 구조 잡거나, 클래스/객체 설계시에 주로 사용됨.
// 유니온 타입
type Status = 'pending' | 'completed' | 'failed';
type Coordinates = [number, number]; // 튜플
type StringOrNumber = string | number // 유니온
// 기존 타입의 매핑
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
// 기본 타입의 별칭
type StringOrNumber = string | number;
// interface 활용
interface User {
name: string;
age: number;
greet(): void;
}
// 확장 가능
interface Employee extends User {
salary: number;
}
// 선언 병합 가능
interface User {
email: string;
}
any
모든 타입을 허용한다. 타입 검사도 하지 않는다.
실수로 잘못된 메서드를 실행해도 에러가 발생하지 않는다.
그냥 없다고 생각해야 한다. 이것을 사용하는 순간 타입 스크립트를 사용하는 근본적인 이유가 사라짐.
unknown
모든 타입을 허용하지만, 사용 전에 타입을 검사한다.
강제적인 타입 좁히기(Type Guard)를 해야지 접근이 가능하다.
외부 API, 동적 데이터 타입 미확정시 추천함. any보다 뭔가 좋아보이지만, 그냥 이것도 최대한 사용하지 않는 거 추천.
// 모든 타입 허용, 타입 체크를 완전히 비활성화
let anyVar: any = 4;
anyVar.toFixed(); // 허용됨
anyVar.nonexistentMethod(); // 허용됨 (위험!)
// 모든 타입 허용하지만, 타입 체크 필수
let unknownVar: unknown = 4;
// unknownVar.toFixed(); // 에러!
// 타입 체크 후 사용 가능함
if (typeof unknownVar === 'number') {
unknownVar.toFixed(); // 허용됨
}
Record<K, T>
객체의 Key-value 쌍 타입 정의
// 사용자 정보 객체
type User = Record<"id" | "username" | "email", string>;
// 도시별 인구수
type PopulationByCity = Record<"Seoul" | "New York" | "Tokyo", number>;
const users: User = {
user1: { id: "1", username: "Ahri", email:"pop_star@aa.com" },
user2: { id: "2", username: "Zoe", email:"bubble@aa.com" },
user3: { id: "3", username: "Izreal", email:"lux@aa.com" },
};
const cityPopulations: PopulationByCity = {
Seoul: 9720846,
New York: 8398748,
Tokyo: 13929286
};
enum / const enum
enum : 런타임에 객체로 남아 커지는 문제가 있다.
컴파일 하고 나서는 큰 크기의 IIFE(즉시 실행 함수)로 변환된다.
이로써 번들 사이즈가 증가되고, Tree-shaking 어려움 발생.
const enum : 컴파일 시 인라인 치환으로 최적화
일반 enum의 경우 런타임에 객체로 존재하지만, const enum은 컴파일 시점에 인라인으로 대체됨. 더 나은 성능과 작은 번들 크기 제공
// 일반 enum (런타임에도 객체로 존재)
enum Direction {
Up = 'UP',
Down = 'DOWN',
Left = 'LEFT',
Right = 'RIGHT'
}
// 숫자 enum
enum StatusCode {
OK = 200,
NotFound = 404,
Error = 500
}
as const
깊은 readonly 적용 + 리터럴 타입 고정.
readonly : 객체나 배열을 수정할 수 없게
특히 불변 데이터를 다룰 때 유용함
컴파일 후 단순 객체로 변환됨
// 리터럴 타입으로 추론되며 읽기 전용
const config = {
api: 'https://api.example.com',
timeout: 3000,
features: ['dark', 'light']
} as const;
// config.timeout = 4000; // 에러: 읽기 전용
type Features = typeof config.features[number]; // 'dark' | 'light'
satisfies
특정 타입을 만족하는지 검사하면서도 값의 리터럴 타입 유지함.
요즘 나온 새로운 기능이다.
const config = {
baseUrl: '/api',
timeout: 3000
} satisfies { baseUrl: string; timeout: number };
String Union
타입 안정성 + 자동완성 제공
가능한 문자열 값들의 집합을 정의함.
컴파일 타임에 타입 안정성 제공해줌.
특히나 상태, 타입, 카테고리 등을 정의할 때 유용
// 가능한 문자열 값들을 제한하는 타입
type Color = 'red' | 'blue' | 'green';
type Size = 'small' | 'medium' | 'large';
// 실제 사용 예시: T셔츠 주문 시스템
interface TShirtOrder {
color: Color;
size: Size;
}
function orderTShirt(order: TShirtOrder) {
console.log(`Ordering ${order.size} ${order.color} T-shirt`);
}
// OK
orderTShirt({ color: 'red', size: 'medium' });
// 에러! 'yellow'는 Color 타입에 없음
orderTShirt({ color: 'yellow', size: 'small' });
Tuple
길이, 위치, 타입이 모두 고정된 배열
구조 분해 할당과의 호환성 : 튜플이기 때문에 정확히 타입이 유지됌.
배열이라면 타입 정보가 손실 될 수 있음.
위치가 의미있는 데이터를 표현할 때 유용
선택적 요소와 나머지 매개변수 지원
type Point = [number, number];
const pos: Point = [10, 20];
Type Guards
런타임에 타입을 확인하는 방법 제공
조건문 내에서 타입을 좁혀줌
사용자 정의 타입 가드로 복잡한 타입 확인 로직 구현 가능
여러 가능한 타입 중에서 실제 타입을 확인하는 방법
// 여러 가능한 타입 중에서 실제 타입을 확인하는 방법입니다
type StringOrNumber = string | number;
function processValue(value: StringOrNumber) {
// Type Guard를 사용하여 타입 확인
if (typeof value === "string") {
// 이 블록에서 value는 string 타입으로 처리됨
console.log(value.toUpperCase());
} else {
// 이 블록에서 value는 number 타입으로 처리됨
console.log(value.toFixed(2));
}
}
processValue("hello"); // "HELLO"
processValue(42); // "42.00"
// 실제 사용예시
// 결제 방법을 처리하는 예시
interface CreditCard {
type: "credit";
cardNumber: string;
expiryDate: string;
}
interface BankTransfer {
type: "bank";
accountNumber: string;
bankCode: string;
}
type PaymentMethod = CreditCard | BankTransfer;
function processPayment(payment: PaymentMethod) {
// Type Guard로 결제 방법 구분
if (payment.type === "credit") {
// 여기서 payment는 CreditCard 타입
console.log(`Processing credit card: ${payment.cardNumber}`);
} else {
// 여기서 payment는 BankTransfer 타입
console.log(`Processing bank transfer: ${payment.accountNumber}`);
}
}
Type Assertions (단언 함수)
특정 값이 어떤 타입임을 조장하도록 개발자가 지정함.
function assertIsNumber(val: unknown): asserts val is number {
if (typeof val !== 'number') throw new Error('Not number');
}
function process(val: unknown) {
assertIsNumber(val);
val.toFixed(); // 여기서 number로 좁혀짐
}
타입스크립트보다 개발자가 타입을 더 잘알고 있을 때 사용
as 키워드나 angle-bracket 문법 사용
위험할 수 있으므로 신중하게 사용.
특히 DOM 조작이나 외부 라이브러리 통합시 자주 사용
type NestedResponseType = {
data: {
categories: [...];
popularMeetings: [...];
newMeetings: [...];
closingTimeActivities: [...];
upcomingActivities?: [...];
mostActivatedRegions: [...];
}
}
type categories = NestedResponseType[’data’][’categories’][number]
타입스크립트에서는 타입 추출 시 에 인덱스 접근법과 infer 패턴을 사용할 수 있다.
NestedResponseType[’data’][’categories’] → 배열 타임
[number]를 붙이면 배열 안의 원소 타입만 추출함.
예를들어 내부에 id: number; name: string; 이렇게 정의되어 있었다면
초반에 data로 추출하면 categories이 외에 모든게 추출이되고
categories로 추출하면 아직 배열 타입으로 id: number 이런게 추출되고
마지막 [number] 추출하면 배열 내부에 객체 타입만 추출됌.
이렇게 추출한 타입은 다른곳에서 재활용도 가능함.
success 속성은 반드시 boolean 타입으로 가져야 하고
ApiResponse 타입은 이렇게 조건부로 분기되는 Discriminated Union 으로 설계하고
type ApiResponse =
| { success: true; data: string[] }
| { success: false; error: Error };
function fetchData(): ApiResponse {
try {
const data = ["apple", "banana", "orange"];
return { success: true, data }; // 성공 시
} catch (err) {
return { success: false, error: new Error("Something went wrong") }; // 실패 시
}
}
const result = fetchData();
if (result.success) {
// result는 자동으로 data 프로퍼티를 가진 타입으로 좁혀짐
console.log("Fetched data:", result.data);
} else {
console.error("Error:", result.error.message);
}
반환 타입이 ApiResponse일 때, if (result.success) 조건문 안에서는 자동으로 data만 접근 가능
반대로 else 블록에서는 error만 접근 가능하도록 타입 안정성이 보장됨.
만약 if (result.success) 조건 안에서 result.error에 접근하거나else 블록에서 result.data에 접근하려 하면 타입 오류가 발생함.