Tan Kim

typescript

TypeScript

JavaScript의 정적 타입 슈퍼셋. 컴파일 타임에 타입 오류를 잡아 대규모 코드베이스 관리에 유리하다.

기본 타입

// Primitive
let name: string = "김탄"
let age: number = 30
let active: boolean = true
let nothing: null = null
let undef: undefined = undefined
 
// 배열
let nums: number[] = [1, 2, 3]
let strs: Array<string> = ["a", "b"]
 
// 튜플
let pair: [string, number] = ["hello", 1]
 
// any / unknown / never
let x: any = "anything"      // 타입 검사 무시 (지양)
let y: unknown = "something" // 사용 전 타입 확인 필요
function error(): never { throw new Error() }  // 반환 없음

타입 정의

// type alias
type UserId = number
type Status = "active" | "inactive" | "pending"  // 유니온
type StringOrNumber = string | number
 
// interface
interface User {
  id: number
  name: string
  email?: string  // 선택적
  readonly createdAt: Date  // 읽기 전용
}
 
// interface 확장
interface Admin extends User {
  role: "admin" | "superadmin"
}
 
// type vs interface: 일반적으로 객체 형태는 interface, 나머지는 type

제네릭

function identity<T>(value: T): T {
  return value
}
 
// 제약 조건
function getLength<T extends { length: number }>(value: T): number {
  return value.length
}
 
// 제네릭 인터페이스
interface ApiResponse<T> {
  data: T
  status: number
  message: string
}
 
// 사용
const res: ApiResponse<User[]> = await fetchUsers()

유틸리티 타입

interface User {
  id: number
  name: string
  email: string
  password: string
}
 
Partial<User>          // 모든 필드 선택적
Required<User>         // 모든 필드 필수
Readonly<User>         // 모든 필드 읽기 전용
Pick<User, 'id' | 'name'>          // 일부 필드만
Omit<User, 'password'>             // 일부 필드 제외
Record<string, number>             // { [key: string]: number }
Exclude<'a' | 'b' | 'c', 'a'>     // 'b' | 'c'
ReturnType<typeof myFn>            // 함수 반환 타입
Parameters<typeof myFn>            // 함수 매개변수 타입

타입 가드

// typeof
function fn(val: string | number) {
  if (typeof val === "string") {
    val.toUpperCase()  // string으로 좁혀짐
  }
}
 
// instanceof
if (error instanceof NetworkError) {}
 
// in 연산자
if ("email" in user) {}
 
// 사용자 정의 타입 가드
function isUser(obj: unknown): obj is User {
  return typeof obj === "object" && obj !== null && "id" in obj
}

클래스

class Service {
  private readonly db: Database
 
  constructor(db: Database) {
    this.db = db
  }
 
  async findById(id: number): Promise<User | null> {
    return this.db.query(`SELECT * FROM users WHERE id = ?`, [id])
  }
}
 
// abstract class
abstract class BaseRepository<T> {
  abstract findById(id: number): Promise<T | null>
 
  async findOrThrow(id: number): Promise<T> {
    const result = await this.findById(id)
    if (!result) throw new Error(`Not found: ${id}`)
    return result
  }
}

tsconfig.json 주요 옵션

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "commonjs",
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "noImplicitReturns": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}

메모