TypeScript 치트시트
TypeScript 핵심 타입 시스템과 고급 패턴 모음 — 기본 타입, 제네릭, 유틸리티 타입, 타입 가드, 고급 타입, 실전 패턴
기본 타입
6 itemsPrimitives
문자열, 숫자, 불리언 및 특수 타입
let name: string = 'Alice'; let age: number = 30; let active: boolean = true; // Special types let data: any = 42; // escape hatch — avoid let input: unknown = getData(); // safer than any let nothing: null = null; let undef: undefined = undefined;
Arrays & Tuples
배열 타입과 고정 길이 튜플 타입
// Arrays const nums: number[] = [1, 2, 3]; const names: Array<string> = ['Alice', 'Bob']; // Readonly array const ids: readonly number[] = [1, 2, 3]; // ids.push(4); // Error // Tuples — fixed length & types const pair: [string, number] = ['age', 30]; const rgb: [number, number, number] = [255, 128, 0]; // Named tuple (documentation only) type Range = [start: number, end: number];
Enums
이름 붙은 상수 집합 — 문자열 enum 권장
// String enum (preferred — readable in output)
enum Status {
Active = 'active',
Inactive = 'inactive',
Pending = 'pending',
}
const s: Status = Status.Active;
// Numeric enum (auto-incrementing)
enum Direction {
Up, // 0
Down, // 1
Left, // 2
Right, // 3
}
// const enum — inlined at compile time
const enum Color { Red, Green, Blue }Union & Literal Types
여러 타입 중 하나를 허용하거나 특정 값만 허용
// Union type
type ID = string | number;
function find(id: ID) { /* ... */ }
// Literal types — restrict to specific values
type Theme = 'light' | 'dark' | 'system';
type Dice = 1 | 2 | 3 | 4 | 5 | 6;
let theme: Theme = 'dark'; // OK
// theme = 'blue'; // Error
// Boolean literal
type Success = { ok: true; data: string };
type Failure = { ok: false; error: string };
type Result = Success | Failure;void / never
void: 반환값 없음, never: 절대 반환하지 않는 함수
// void — no return value
function log(msg: string): void {
console.log(msg);
}
// never — exhaustive checks / throws
function throwError(msg: string): never {
throw new Error(msg);
}
// never in exhaustive switch
function handleShape(shape: Circle | Square): number {
switch (shape.kind) {
case 'circle': return Math.PI * shape.r ** 2;
case 'square': return shape.s ** 2;
default:
const _: never = shape; // compile error if case missed
return _;
}
}Type Assertions
컴파일러에게 타입을 명시적으로 알려주는 방법
// as syntax (preferred)
const input = document.getElementById('name') as HTMLInputElement;
input.value = 'Hello';
// Angle bracket syntax (not in JSX)
const el = <HTMLDivElement>document.querySelector('.box');
// const assertion — narrows to literal
const config = { endpoint: '/api', retries: 3 } as const;
// type: { readonly endpoint: '/api'; readonly retries: 3 }
// Non-null assertion (use sparingly)
const el2 = document.getElementById('app')!;인터페이스 & 타입
6 itemsInterface
객체 구조를 정의하는 계약 — 확장(extends) 및 병합 가능
interface User {
id: number;
name: string;
email: string;
avatar?: string; // optional
readonly createdAt: Date; // immutable after creation
}
// Extend
interface Admin extends User {
role: 'admin' | 'superadmin';
permissions: string[];
}
// Declaration merging (unique to interfaces)
interface Window {
analytics: AnalyticsSDK;
}Type Alias
타입에 이름을 부여 — 유니온, 튜플, 함수 타입 등에 사용
// Object type
type Point = { x: number; y: number };
// Union (interface cannot do this)
type Result<T> = { ok: true; data: T } | { ok: false; error: string };
// Function type
type Handler = (event: Event) => void;
// Tuple
type Coordinate = [lat: number, lng: number];
// Template literal type
type EventName = `on${'Click' | 'Hover' | 'Focus'}`;
// 'onClick' | 'onHover' | 'onFocus'Intersection (&)
여러 타입을 결합하여 모든 속성을 포함하는 타입 생성
type Timestamps = {
createdAt: Date;
updatedAt: Date;
};
type SoftDelete = {
deletedAt: Date | null;
};
// Combine all
type BaseEntity = Timestamps & SoftDelete & { id: string };
// With generics
type ApiResponse<T> = {
data: T;
meta: { page: number; total: number };
} & Timestamps;Index Signatures
동적 키를 가진 객체의 타입 정의
// String index
interface StringMap {
[key: string]: string;
}
// Mixed known + dynamic
interface Config {
name: string;
version: number;
[key: string]: string | number | boolean; // catch-all
}
// Record utility (cleaner alternative)
type Env = Record<string, string>;
// Map-like with specific value types
type Translations = Record<'en' | 'ko' | 'ja', string>;Readonly & Partial Patterns
불변 타입과 선택적 속성 타입 만들기
interface Config {
host: string;
port: number;
debug: boolean;
}
// All readonly
const config: Readonly<Config> = {
host: 'localhost', port: 3000, debug: false,
};
// config.port = 8080; // Error
// All optional (for updates)
function updateConfig(patch: Partial<Config>) {
Object.assign(config, patch);
}
updateConfig({ debug: true }); // only pass what changed
// Deep readonly
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};Extending & Composing
인터페이스 상속 및 타입 조합 패턴
// Multiple extends
interface Loggable { log(): void; }
interface Serializable { toJSON(): string; }
interface Entity extends Loggable, Serializable {
id: string;
}
// Omit to override properties
interface CreateUserDTO extends Omit<User, 'id' | 'createdAt'> {
password: string;
}
// Pick for subset
type UserPreview = Pick<User, 'id' | 'name' | 'avatar'>;제네릭
6 itemsGeneric Functions
다양한 타입에서 작동하는 재사용 가능한 함수
// Basic generic function
function first<T>(arr: T[]): T | undefined {
return arr[0];
}
first<string>(['a', 'b']); // type: string
first([1, 2, 3]); // type: number (inferred)
// Multiple type parameters
function zip<A, B>(a: A[], b: B[]): [A, B][] {
return a.map((val, i) => [val, b[i]]);
}
zip(['a', 'b'], [1, 2]); // [['a', 1], ['b', 2]]Generic Constraints (extends)
제네릭 타입을 특정 구조로 제한
// Must have .length
function longest<T extends { length: number }>(a: T, b: T): T {
return a.length >= b.length ? a : b;
}
longest('hello', 'hi'); // 'hello'
longest([1, 2], [1, 2, 3]); // [1, 2, 3]
// Must have .id
function findById<T extends { id: string }>(items: T[], id: string): T | undefined {
return items.find(item => item.id === id);
}
// keyof constraint
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}Generic Interfaces & Types
재사용 가능한 제네릭 데이터 구조
// API response wrapper
interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
// Paginated response
interface Paginated<T> {
items: T[];
total: number;
page: number;
pageSize: number;
hasMore: boolean;
}
// Usage
type UserResponse = ApiResponse<User>;
type UserList = Paginated<User>;
async function fetchUsers(): Promise<Paginated<User>> {
const res = await fetch('/api/users');
return res.json();
}Default Generics
제네릭 매개변수에 기본 타입 지정
// Default type parameter
interface Store<T = Record<string, unknown>> {
get(key: string): T;
set(key: string, value: T): void;
}
// No type param needed
const store: Store = {
get: (key) => ({}),
set: (key, value) => {},
};
// Event emitter with defaults
type EventMap = Record<string, unknown>;
class Emitter<Events extends EventMap = EventMap> {
on<K extends keyof Events>(event: K, handler: (data: Events[K]) => void) { /* ... */ }
emit<K extends keyof Events>(event: K, data: Events[K]) { /* ... */ }
}Generic Classes
타입 안전한 제네릭 클래스
class TypedMap<K, V> {
private items = new Map<K, V>();
set(key: K, value: V): void {
this.items.set(key, value);
}
get(key: K): V | undefined {
return this.items.get(key);
}
has(key: K): boolean {
return this.items.has(key);
}
values(): V[] {
return [...this.items.values()];
}
}
const cache = new TypedMap<string, User>();
cache.set('alice', { id: 1, name: 'Alice' });
const user = cache.get('alice'); // User | undefinedUtility Types with Generics
제네릭을 활용한 유틸리티 타입 만들기
// Make specific fields optional
type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
type CreateUser = Optional<User, 'id' | 'createdAt'>;
// Make specific fields required
type RequireFields<T, K extends keyof T> = T & Required<Pick<T, K>>;
// Nullable
type Nullable<T> = { [K in keyof T]: T[K] | null };
// Deep partial
type DeepPartial<T> = {
[K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
};유틸리티 타입
6 itemsPartial / Required
Partial: 모든 속성을 선택적으로, Required: 모든 속성을 필수로
interface User {
name: string;
email: string;
age?: number;
}
// Partial — all optional
function updateUser(id: string, patch: Partial<User>) { /* ... */ }
updateUser('1', { name: 'Bob' }); // OK — only name
// Required — all required (removes ?)
const completeUser: Required<User> = {
name: 'Alice',
email: 'alice@example.com',
age: 30, // required now
};Pick / Omit
Pick: 특정 속성만 선택, Omit: 특정 속성 제외
interface User {
id: string;
name: string;
email: string;
password: string;
createdAt: Date;
}
// Pick — keep only these
type PublicUser = Pick<User, 'id' | 'name'>;
// { id: string; name: string }
// Omit — remove these
type SafeUser = Omit<User, 'password'>;
// { id, name, email, createdAt }
// Combine for DTOs
type CreateUserDTO = Omit<User, 'id' | 'createdAt'>;
type UpdateUserDTO = Partial<Omit<User, 'id'>>;Record
키-값 쌍의 객체 타입을 간결하게 정의
// String → value mapping
type Env = Record<string, string>;
const env: Env = { NODE_ENV: 'production', PORT: '3000' };
// Union key → value
type StatusMap = Record<'active' | 'inactive' | 'pending', User[]>;
// Enum key
type ThemeColors = Record<Theme, { bg: string; text: string }>;
// Common pattern: lookup table
const statusLabels: Record<Status, string> = {
active: 'Active',
inactive: 'Inactive',
pending: 'Pending',
};Exclude / Extract
Exclude: 유니온에서 타입 제거, Extract: 유니온에서 타입 추출
type Events = 'click' | 'scroll' | 'mousemove' | 'keypress';
// Remove from union
type MouseEvents = Exclude<Events, 'keypress'>;
// 'click' | 'scroll' | 'mousemove'
// Keep matching
type KeyEvents = Extract<Events, 'keypress'>;
// 'keypress'
// Practical: filter union types
type Shape = Circle | Square | Triangle;
type RoundShape = Extract<Shape, { kind: 'circle' }>;
// NonNullable = Exclude<T, null | undefined>
type Name = NonNullable<string | null | undefined>;
// stringReturnType / Parameters
함수의 반환 타입 또는 매개변수 타입 추출
function createUser(name: string, age: number) {
return { id: crypto.randomUUID(), name, age, active: true };
}
// Extract return type
type User = ReturnType<typeof createUser>;
// { id: string; name: string; age: number; active: boolean }
// Extract parameter types
type CreateParams = Parameters<typeof createUser>;
// [name: string, age: number]
// Awaited — unwrap Promise
type Data = Awaited<Promise<Promise<string>>>;
// string
// ConstructorParameters
type DateArgs = ConstructorParameters<typeof Date>;Awaited / InstanceType
Awaited: Promise 언래핑, InstanceType: 클래스 인스턴스 타입
// Awaited — deeply unwraps Promise
type A = Awaited<Promise<string>>; // string
type B = Awaited<Promise<Promise<number>>>; // number
async function fetchData() { return { users: [] as User[] }; }
type FetchResult = Awaited<ReturnType<typeof fetchData>>;
// { users: User[] }
// InstanceType
class Logger {
log(msg: string) { console.log(msg); }
}
type LoggerInstance = InstanceType<typeof Logger>;
function createService<T extends new (...args: any[]) => any>(
Cls: T
): InstanceType<T> {
return new Cls();
}타입 가드
6 itemstypeof / instanceof
런타임 타입 체크로 타입 좁히기
// typeof
function format(value: string | number): string {
if (typeof value === 'string') {
return value.toUpperCase(); // TS knows: string
}
return value.toFixed(2); // TS knows: number
}
// instanceof
function getArea(shape: Circle | Rectangle) {
if (shape instanceof Circle) {
return Math.PI * shape.radius ** 2; // Circle
}
return shape.width * shape.height; // Rectangle
}Discriminated Unions
공통 판별 속성으로 유니온 타입을 구분하는 패턴
type Shape =
| { kind: 'circle'; radius: number }
| { kind: 'square'; side: number }
| { kind: 'rectangle'; width: number; height: number };
function area(shape: Shape): number {
switch (shape.kind) {
case 'circle': return Math.PI * shape.radius ** 2;
case 'square': return shape.side ** 2;
case 'rectangle': return shape.width * shape.height;
}
}
// API result pattern
type Result<T> =
| { success: true; data: T }
| { success: false; error: string };
function handleResult(r: Result<User>) {
if (r.success) {
console.log(r.data.name); // narrowed to success
} else {
console.error(r.error); // narrowed to failure
}
}Type Predicates (is)
사용자 정의 타입 가드 함수
// is keyword narrows the type
function isString(value: unknown): value is string {
return typeof value === 'string';
}
function isUser(obj: unknown): obj is User {
return (
obj !== null &&
typeof obj === 'object' &&
'id' in obj &&
'name' in obj
);
}
// Usage — TS narrows after check
const items: unknown[] = [1, 'hello', { id: 1, name: 'Alice' }];
const strings = items.filter(isString);
// type: string[]in Operator
속성 존재 여부로 타입 좁히기
type Fish = { swim: () => void };
type Bird = { fly: () => void };
function move(animal: Fish | Bird) {
if ('swim' in animal) {
animal.swim(); // TS knows: Fish
} else {
animal.fly(); // TS knows: Bird
}
}
// With optional properties
type Admin = { role: string; permissions: string[] };
type Guest = { sessionId: string };
function greet(user: Admin | Guest) {
if ('role' in user) {
console.log(`Admin: ${user.role}`);
}
}satisfies Operator
타입을 검증하면서 추론된 타입을 유지 (TS 4.9+)
// Without satisfies — type widens
const routes: Record<string, string> = {
home: '/', about: '/about', contact: '/contact',
};
routes.home; // type: string (can't narrow)
// With satisfies — validates but keeps literal types
const routes2 = {
home: '/',
about: '/about',
contact: '/contact',
} satisfies Record<string, string>;
routes2.home; // type: '/' (literal preserved!)
// routes2.missing; // Error — key doesn't existAssertion Functions
조건이 거짓이면 예외를 던지는 타입 단언 함수
function assertDefined<T>(
value: T | null | undefined,
name: string
): asserts value is T {
if (value === null || value === undefined) {
throw new Error(`${name} is not defined`);
}
}
// After assertion, TS narrows the type
const user = getUser(); // User | null
assertDefined(user, 'user');
console.log(user.name); // TS knows: User (not null)
function assertString(val: unknown): asserts val is string {
if (typeof val !== 'string') throw new TypeError('Expected string');
}고급 타입
6 itemsConditional Types
조건에 따라 다른 타입을 반환하는 타입 수준 삼항 연산
// Basic conditional type IsString<T> = T extends string ? true : false; type A = IsString<'hello'>; // true type B = IsString<42>; // false // Practical: unwrap array type Flatten<T> = T extends (infer U)[] ? U : T; type Num = Flatten<number[]>; // number type Str = Flatten<string>; // string // Distributive conditional type NonNullable<T> = T extends null | undefined ? never : T; type Clean = NonNullable<string | null | undefined>; // string
Mapped Types
기존 타입의 각 속성을 변환하여 새 타입 생성
// Make all properties optional (Partial implementation)
type MyPartial<T> = {
[K in keyof T]?: T[K];
};
// Make all properties readonly
type MyReadonly<T> = {
readonly [K in keyof T]: T[K];
};
// Transform property types
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
type UserGetters = Getters<{ name: string; age: number }>;
// { getName: () => string; getAge: () => number }Template Literal Types
문자열 리터럴 타입을 조합하여 새 타입 생성
// Basic concatenation
type Greeting = `Hello, ${'World' | 'TypeScript'}`;
// 'Hello, World' | 'Hello, TypeScript'
// Event name generation
type EventName = `${'click' | 'scroll' | 'keydown'}Handler`;
// 'clickHandler' | 'scrollHandler' | 'keydownHandler'
// CSS property pattern
type CSSUnit = `${number}${'px' | 'rem' | 'em' | '%'}`;
const width: CSSUnit = '100px'; // OK
// const bad: CSSUnit = '100vw'; // Error
// Intrinsic string manipulation
type Upper = Uppercase<'hello'>; // 'HELLO'
type Lower = Lowercase<'HELLO'>; // 'hello'
type Cap = Capitalize<'hello'>; // 'Hello'
type Uncap = Uncapitalize<'Hello'>; // 'hello'infer Keyword
조건부 타입 내에서 타입 변수를 추론하여 추출
// Extract array element type
type ElementOf<T> = T extends (infer U)[] ? U : never;
type Item = ElementOf<string[]>; // string
// Extract Promise value
type Unwrap<T> = T extends Promise<infer U> ? U : T;
type Data = Unwrap<Promise<{ users: User[] }>>;
// { users: User[] }
// Extract function return type (ReturnType impl)
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
// Extract first argument
type FirstArg<T> = T extends (first: infer F, ...rest: any[]) => any ? F : never;
type Arg = FirstArg<(name: string, age: number) => void>;
// stringkeyof / typeof
keyof: 객체 키의 유니온, typeof: 값에서 타입 추출
interface User {
id: number;
name: string;
email: string;
}
type UserKeys = keyof User; // 'id' | 'name' | 'email'
// typeof — extract type from a value
const config = {
api: 'https://api.example.com',
timeout: 5000,
retries: 3,
};
type Config = typeof config;
// { api: string; timeout: number; retries: number }
// Combine keyof + typeof
type ConfigKey = keyof typeof config;
// 'api' | 'timeout' | 'retries'
function getSetting(key: keyof typeof config) {
return config[key];
}as const
값을 가장 좁은 리터럴 타입으로 고정
// Without as const
const colors = ['red', 'green', 'blue'];
// type: string[]
// With as const
const colors2 = ['red', 'green', 'blue'] as const;
// type: readonly ['red', 'green', 'blue']
type Color = (typeof colors2)[number];
// 'red' | 'green' | 'blue'
// Object as const
const STATUS = {
ACTIVE: 'active',
INACTIVE: 'inactive',
} as const;
type StatusValue = (typeof STATUS)[keyof typeof STATUS];
// 'active' | 'inactive'함수 & 클래스
5 itemsFunction Overloads
같은 함수에 여러 시그니처 정의 — 입력에 따라 반환 타입 변경
// Overload signatures
function createElement(tag: 'input'): HTMLInputElement;
function createElement(tag: 'canvas'): HTMLCanvasElement;
function createElement(tag: string): HTMLElement;
// Implementation signature
function createElement(tag: string): HTMLElement {
return document.createElement(tag);
}
const input = createElement('input'); // HTMLInputElement
const canvas = createElement('canvas'); // HTMLCanvasElement
const div = createElement('div'); // HTMLElementGeneric Functions
타입 안전한 범용 함수 패턴
// Type-safe object getter
function get<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { name: 'Alice', age: 30 };
const name = get(user, 'name'); // string
const age = get(user, 'age'); // number
// get(user, 'foo'); // Error — 'foo' not in keyof User
// Type-safe API wrapper
async function fetchJSON<T>(url: string): Promise<T> {
const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json() as Promise<T>;
}
const users = await fetchJSON<User[]>('/api/users');Abstract Classes
추상 클래스 — 서브클래스가 반드시 구현해야 하는 계약
abstract class Shape {
abstract area(): number;
abstract perimeter(): number;
// Concrete method
describe(): string {
return `Area: ${this.area().toFixed(2)}, Perimeter: ${this.perimeter().toFixed(2)}`;
}
}
class Circle extends Shape {
constructor(private radius: number) { super(); }
area() { return Math.PI * this.radius ** 2; }
perimeter() { return 2 * Math.PI * this.radius; }
}
// const s = new Shape(); // Error — cannot instantiate abstract
const c = new Circle(5);
c.describe(); // 'Area: 78.54, Perimeter: 31.42'Access Modifiers
public, private, protected, readonly 접근 제어자
class BankAccount {
public owner: string;
private balance: number;
protected currency: string;
readonly accountId: string;
constructor(owner: string, initial: number) {
this.owner = owner;
this.balance = initial;
this.currency = 'USD';
this.accountId = crypto.randomUUID();
}
// Parameter properties shorthand
// constructor(
// public owner: string,
// private balance: number,
// ) {}
getBalance(): number { return this.balance; }
}implements
클래스가 인터페이스 계약을 준수하는지 검증
interface Logger {
info(message: string): void;
error(message: string, error?: Error): void;
warn(message: string): void;
}
class ConsoleLogger implements Logger {
info(message: string) { console.log(`[INFO] ${message}`); }
error(message: string, error?: Error) {
console.error(`[ERROR] ${message}`, error);
}
warn(message: string) { console.warn(`[WARN] ${message}`); }
}
// Multiple interfaces
interface Serializable { serialize(): string; }
interface Cacheable { cacheKey(): string; }
class UserModel implements Serializable, Cacheable {
serialize() { return JSON.stringify(this); }
cacheKey() { return `user:${this.id}`; }
}패턴 & 팁
6 itemsExhaustive Check (never)
switch/if에서 모든 케이스를 처리했는지 컴파일 타임에 검증
type Status = 'active' | 'inactive' | 'pending';
function assertNever(x: never): never {
throw new Error(`Unexpected value: ${x}`);
}
function getLabel(status: Status): string {
switch (status) {
case 'active': return 'Active';
case 'inactive': return 'Inactive';
case 'pending': return 'Pending';
default: return assertNever(status);
// If you add 'archived' to Status, this will error
// until you add its case
}
}Branded Types
같은 기본 타입이지만 의미적으로 구분되는 타입 만들기
// Prevent mixing up IDs
type UserId = string & { readonly __brand: 'UserId' };
type PostId = string & { readonly __brand: 'PostId' };
function createUserId(id: string): UserId { return id as UserId; }
function createPostId(id: string): PostId { return id as PostId; }
function getUser(id: UserId) { /* ... */ }
function getPost(id: PostId) { /* ... */ }
const userId = createUserId('user-123');
const postId = createPostId('post-456');
getUser(userId); // OK
// getUser(postId); // Error — PostId is not UserIdBuilder Pattern
체이닝 API로 타입 안전한 객체 생성
class QueryBuilder<T extends Record<string, any>> {
private query: Partial<T> = {};
where<K extends keyof T>(key: K, value: T[K]): this {
this.query[key] = value;
return this;
}
build(): Partial<T> {
return { ...this.query };
}
}
interface UserQuery {
name: string;
age: number;
active: boolean;
}
const query = new QueryBuilder<UserQuery>()
.where('name', 'Alice')
.where('active', true)
// .where('name', 42) // Error — 42 is not string
.build();Type-safe Event Emitter
이벤트 이름과 페이로드를 타입으로 검증하는 이벤트 시스템
type EventMap = {
login: { userId: string; timestamp: number };
logout: { userId: string };
error: { message: string; code: number };
};
class TypedEmitter<T extends Record<string, any>> {
private handlers = new Map<keyof T, Set<Function>>();
on<K extends keyof T>(event: K, handler: (data: T[K]) => void) {
if (!this.handlers.has(event)) this.handlers.set(event, new Set());
this.handlers.get(event)!.add(handler);
}
emit<K extends keyof T>(event: K, data: T[K]) {
this.handlers.get(event)?.forEach(h => h(data));
}
}
const events = new TypedEmitter<EventMap>();
events.on('login', ({ userId }) => console.log(userId));
// events.emit('login', { message: '' }); // ErrorZod Integration
Zod 스키마에서 TypeScript 타입 자동 추론
import { z } from 'zod';
// Define schema
const UserSchema = z.object({
id: z.string().uuid(),
name: z.string().min(1).max(100),
email: z.string().email(),
age: z.number().int().min(0).max(150).optional(),
role: z.enum(['admin', 'user', 'guest']),
});
// Infer type from schema
type User = z.infer<typeof UserSchema>;
// { id: string; name: string; email: string; age?: number; role: 'admin' | 'user' | 'guest' }
// Runtime validation
const result = UserSchema.safeParse(input);
if (result.success) {
console.log(result.data.name); // fully typed
} else {
console.error(result.error.issues);
}Module Augmentation
기존 모듈이나 전역 타입을 확장
// Extend Express Request
declare module 'express' {
interface Request {
user?: { id: string; role: string };
requestId: string;
}
}
// Extend Window
declare global {
interface Window {
__APP_VERSION__: string;
analytics: {
track(event: string, data?: Record<string, unknown>): void;
};
}
}
// Extend existing enum
declare module './types' {
interface UserRoles {
superadmin: 'superadmin';
}
}
export {}; // Make this a moduleTypeScript 치트시트 사용 가이드
TypeScript는 JavaScript에 정적 타입 시스템을 추가하여 개발 생산성과 코드 품질을 크게 향상시킵니다. 이 치트시트는 실무에서 가장 자주 필요한 타입 패턴을 빠르게 참고할 수 있도록 정리했습니다.
제네릭은 왜 중요한가요?
제네릭은 타입 정보를 잃지 않으면서 재사용 가능한 코드를 작성하는 핵심 도구입니다. API 응답 래퍼, 컬렉션 유틸리티, 상태 관리 등에서 any 대신 제네릭을 사용하면 자동완성과 타입 체크의 혜택을 동시에 받을 수 있습니다.
유틸리티 타입 활용하기
Partial, Pick, Omit, Record 등의 내장 유틸리티 타입은 기존 타입에서 새 타입을 파생할 때 필수적입니다. 예를 들어 API의 Create DTO는 Omit<User, "id" | "createdAt">로, Update DTO는 Partial<Omit<User, "id">>로 간결하게 정의할 수 있습니다.
타입 안전성을 높이는 패턴
판별 유니온, 브랜드 타입, 철저한 검사(exhaustive check) 등의 패턴을 활용하면 런타임 에러를 컴파일 타임에 미리 잡을 수 있습니다. 특히 switch 문에서 never 타입을 활용한 철저한 검사는 새로운 케이스 추가 시 누락을 자동으로 감지해 줍니다.