SHIN STORYSHIN STORY
홈포스트C#TypeScriptNext.jsNode.js시리즈
</>SHIN STORY

sdf

탐색

  • 홈
  • 모든 포스트
  • 시리즈
  • 검색

카테고리

  • C#
  • TypeScript
  • Next.js
  • Node.js
  • 알고리즘
  • 개발 도구

© 2025 Shin Blog. All rights reserved.

GitHubRSS
목록으로
TypeScript#TypeScript

TypeScript 팁 #10 — 타입 성능 최적화와 실무 패턴

SHIN

2025년 5월 3일3분 읽기0
📚

TypeScript 실전 팁 10선

10편
  1. 5TypeScript 팁 #5 — 타입 가드로 런타임 타입 좁히기
  2. 6TypeScript 팁 #6 — 매핑 타입으로 타입 변환하기
  3. 10TypeScript 팁 #1 — strict 모드를 반드시 켜야 하는 이유
  4. 10TypeScript 팁 #2 — 유틸리티 타입 완전 정복
  5. 10TypeScript 팁 #3 — 판별 유니온으로 타입 안전한 상태 관리
  6. 10TypeScript 팁 #4 — 제네릭 제약 조건과 infer 활용
  7. 10TypeScript 팁 #7 — 템플릿 리터럴 타입으로 문자열 타입 조작
  8. 10TypeScript 팁 #8 — 선언 병합과 모듈 보강으로 타입 확장하기
  9. 10TypeScript 팁 #9 — 타입 안전한 에러 처리 패턴
  10. TypeScript 팁 #10 — 타입 성능 최적화와 실무 패턴현재

TypeScript 팁 #10 — 타입 성능 최적화와 실무 패턴

satisfies 연산자 (TypeScript 4.9)

as 없이 타입 검증과 좁은 타입 추론을 동시에 얻습니다.

CODE
type Color = 'red' | 'green' | 'blue'
type Palette = Record<string, Color | Color[]>

// ❌ as로 강제 캐스팅 — 추론 정보 손실
const palette = {
  primary: 'red',
  secondary: ['green', 'blue'],
} as Palette

palette.primary.toUpperCase()  // ❌ Color | Color[]이라 .toUpperCase() 없음

// ✅ satisfies — 타입 검사 + 추론 유지
const palette2 = {
  primary: 'red',
  secondary: ['green', 'blue'],
} satisfies Palette

palette2.primary.toUpperCase()   // ✅ 'red'로 좁혀짐 (string)
palette2.secondary.map(c => c)  // ✅ Color[]로 좁혀짐

const assertion — 리터럴 타입 보존

CODE
// as const 없이 — 넓은 타입
const config = {
  endpoint: '/api/v1',
  methods: ['GET', 'POST'],
}
// { endpoint: string; methods: string[] }

// as const — 리터럴 타입 유지
const config2 = {
  endpoint: '/api/v1',
  methods: ['GET', 'POST'],
} as const
// { readonly endpoint: '/api/v1'; readonly methods: readonly ['GET', 'POST'] }

// 활용: 객체에서 유니온 타입 추출
const STATUS = {
  PENDING: 'pending',
  ACTIVE: 'active',
  INACTIVE: 'inactive',
} as const

type Status = typeof STATUS[keyof typeof STATUS]
// 'pending' | 'active' | 'inactive'

브랜드 타입 — 원시 타입 구별

CODE
// ❌ 실수로 ID를 바꿔 넘겨도 컴파일 통과
function transfer(fromId: number, toId: number, amount: number) {}
transfer(userId, amount, toId)  // 순서 바꿔도 오류 없음

// ✅ 브랜드 타입으로 구별
type UserId   = number & { readonly _brand: 'UserId' }
type AccountId = number & { readonly _brand: 'AccountId' }
type Amount   = number & { readonly _brand: 'Amount' }

function asUserId(id: number): UserId       { return id as UserId }
function asAccountId(id: number): AccountId { return id as AccountId }
function asAmount(n: number): Amount        { return n as Amount }

function transfer(from: UserId, to: UserId, amount: Amount) {}

transfer(asUserId(1), asUserId(2), asAmount(100))  // ✅
transfer(asUserId(1), asAmount(100), asUserId(2))  // ❌ 타입 불일치

타입 성능 최적화

복잡한 타입 연산은 컴파일 시간을 늘립니다.

CODE
// ❌ 느림 — 재귀 깊이가 깊은 분산 조건부 타입
type DeepNested<T> = T extends object
  ? { [K in keyof T]: DeepNested<T[K]> }
  : T

// ✅ 빠름 — interface 캐싱 활용
interface DeepNestedI<T extends object> {
  [K in keyof T]: T[K] extends object ? DeepNestedI<T[K]> : T[K]
}
CODE
// ❌ 느림 — 모든 타입을 분산시키는 유니온
type UnionToIntersection<U> =
  (U extends any ? (x: U) => void : never) extends (x: infer I) => void ? I : never

// 타입 복잡도를 측정하는 방법: tsc --extendedDiagnostics
// Types: 큰 숫자면 타입 최적화 필요

불필요한 any 대신 unknown 사용 지침

CODE
// any: 타입 검사 완전 비활성화 — 최후의 수단
function processAny(data: any) {
  data.anything()  // 컴파일 통과 — 위험
}

// unknown: 사용 전 좁히기 강제
function processUnknown(data: unknown) {
  data.anything()  // ❌ 컴파일 오류

  if (typeof data === 'object' && data !== null) {
    // 여기서 data는 object로 좁혀짐
  }
}

// 제네릭: 호출 시점에 타입 결정 — 가장 좋음
function processGeneric<T>(data: T): T {
  return data
}

타입 유틸리티 모음 — 실무 자주 사용

CODE
// null/undefined 제거
type NonNullable<T> = T & {}  // 내장 유틸리티와 동일

// 배열 요소 타입
type ArrayElement<T> = T extends (infer U)[] ? U : never

// 객체 값 타입
type ValueOf<T> = T[keyof T]

// 특정 값 타입의 키만 추출
type KeysOfType<T, V> = {
  [K in keyof T]: T[K] extends V ? K : never
}[keyof T]

interface Form {
  name: string
  age: number
  email: string
  active: boolean
}

type StringFieldKeys = KeysOfType<Form, string>  // 'name' | 'email'
type NumberFieldKeys = KeysOfType<Form, number>  // 'age'

// 재귀 Partial
type DeepPartial<T> = {
  [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K]
}

TypeScript의 타입 시스템은 단순한 문서화 도구가 아니라 버그를 예방하는 실질적인 안전망입니다. 이 10가지 팁을 프로젝트에 점진적으로 적용해보세요.

공유
S

SHIN

.NET 개발자입니다

GitHub
TypeScript 팁 #9 — 타입 안전한 에러 처리 패턴

이전 포스트

TypeScript 팁 #9 — 타입 안전한 에러 처리 패턴

다음 포스트

Node.js Event Loop 완전 정복

Node.js Event Loop 완전 정복

같은 카테고리 포스트

TypeScript 팁 #9 — 타입 안전한 에러 처리 패턴

TypeScript 팁 #9 — 타입 안전한 에러 처리 패턴

2025년 5월 1일· 2분
TypeScript 팁 #8 — 선언 병합과 모듈 보강으로 타입 확장하기

TypeScript 팁 #8 — 선언 병합과 모듈 보강으로 타입 확장하기

2025년 4월 29일· 2분
TypeScript 팁 #7 — 템플릿 리터럴 타입으로 문자열 타입 조작

TypeScript 팁 #7 — 템플릿 리터럴 타입으로 문자열 타입 조작

2025년 4월 27일· 2분

댓글