TypeScript 타입 가드

발행: (2026년 4월 6일 PM 08:25 GMT+9)
8 분 소요
원문: Dev.to

I’m happy to translate the article for you, but I’ll need the full text of the post (the portions you’d like translated). Please paste the article’s content here, and I’ll provide a Korean translation while preserving the source link, formatting, markdown, and any code blocks or URLs unchanged.

결제 시스템을 구축할 때, “대충 맞다”는 충분하지 않다

하나의 undefined 값이나 일치하지 않는 객체 속성이 성공적인 거래와 좌절한 고객(또는 손실된 판매) 사이의 차이가 될 수 있습니다.

TypeScript의 Type Guards는 첫 번째 방어선입니다. 이들은 넓고 불확실한 타입을 안전하게 상호작용할 수 있는 구체적인 타입으로 좁혀줍니다. 이 가이드에서는 미니 결제 프로세서를 만들고 Type Guards를 사용해 충돌 방지 코드를 작성하는 방법을 배웁니다.

1. 문제: JavaScript의 “Silent Failures”

다양한 종류의 결제 응답을 처리하는 함수를 만든다고 가정해 보세요. 순수 JavaScript에서는 다음과 같이 작성할 수 있습니다:

function processResponse(response) {
  // If response is a 'Success' object, it has a 'transactionId'
  // If it's an 'Error' object, it has a 'message'
  console.log("Payment successful! ID: " + response.transactionId);
}

// What if the API returned an error?
processResponse({ message: "Insufficient funds" });
// Output: "Payment successful! ID: undefined"

JavaScript는 별다른 경고를 표시하지 않고 undefined를 반환합니다. 이것이 silent failure입니다. TypeScript를 사용하면—우리가 타입을 narrow 하는 방법을 안다면—이 문제를 잡아낼 수 있습니다.

Source:

2. 내장 가드: 기초

TypeScript는 런타임 검사를 수행하는 내장 연산자를 제공합니다. TypeScript가 이러한 검사를 만나면, 해당 코드 블록 전체에 대해 타입을 좁혀 줍니다.

typeof (원시 타입 검사)

우리 결제 앱에서 amountnumber이거나 string일 수 있습니다(폼 입력에서 오는 경우).

function formatAmount(amount: string | number) {
  if (typeof amount === "string") {
    // TypeScript knows `amount` is a string here.
    // We can safely call string‑specific methods.
    return parseFloat(amount).toFixed(2);
  }
  // Here TypeScript knows `amount` must be a number.
  return amount.toFixed(2);
}

instanceof (클래스 검사)

CreditCardGiftCard 결제에 대해 서로 다른 클래스를 가지고 있다고 가정해 봅시다. 각각은 고유한 검증 로직을 가집니다.

class CreditCard {
  verifyCVV() {
    return true;
  }
}

class GiftCard {
  checkBalance() {
    return 50.0;
  }
}

function verifyPayment(method: CreditCard | GiftCard) {
  if (method instanceof CreditCard) {
    // Safe to call CreditCard methods
    method.verifyCVV();
  } else {
    // Safe to call GiftCard methods
    method.checkBalance();
  }
}

3. Custom Type Predicates: The “Bouncer”

때로는 단순한 검사만으로는 충분하지 않을 때가 있습니다. 결제가 완료되었는지 확인하는 재사용 가능한 함수를 만들고 싶을 수도 있죠. 바로 Custom Type Predicate가 필요한 상황입니다.

interface Payment {
  id: string;
  status: "pending" | "completed" | "failed";
}

interface CompletedPayment extends Payment {
  status: "completed";
  confirmedAt: Date;
}

/* This is our Custom Type Predicate */
function isCompleted(payment: Payment): payment is CompletedPayment {
  return payment.status === "completed";
}

const myPayment: Payment = { id: "123", status: "completed" };

if (isCompleted(myPayment)) {
  // TypeScript now knows `myPayment` has a `confirmedAt` property!
  console.log(myPayment.confirmedAt);
}

위 예시에서 isCompleted 함수는 Custom Type Predicate 역할을 하여, paymentCompletedPayment 타입인지 검사합니다. if (isCompleted(myPayment)) 블록 안으로 들어가면 TypeScript가 myPaymentCompletedPayment임을 알게 되므로, confirmedAt 같은 추가 속성에 안전하게 접근할 수 있습니다.

4. 차별화된 유니온: 골드 스탠다드

이 글에서 하나만 배운다면 바로 이 패턴을 선택하세요. 타입에 단 하나의 공통 속성(구분자)을 추가하면 100 % 타입 안전성과 완벽한 IDE 자동완성을 얻을 수 있습니다.

interface CardPayment {
  type: "card"; // Discriminator
  lastFour: string;
}

interface PayPalPayment {
  type: "paypal"; // Discriminator
  email: string;
}

type PaymentMethod = CardPayment | PayPalPayment;

function getReceipt(method: PaymentMethod) {
  switch (method.type) {
    case "card":
      return `Charged card ending in ${method.lastFour}`;
    case "paypal":
      return `Charged PayPal account: ${method.email}`;
  }
}

왜 이것이 초능력인가: 나중에 Bitcoin 결제 타입을 추가했지만 switch 문을 업데이트하지 않으면 TypeScript가 즉시 오류를 표시합니다. 마치 시니어 개발자가 당신의 어깨 너머를 살펴보는 것과 같습니다.

5. 안티‑패턴: “전과 후”

초보자라면 빨간 오류 라인을 없애기 위해 바로가기를 사용하고 싶을 수 있습니다. 왜 그렇게 하면 안 되는지 살펴봅시다.

“안전하지 않은 단언”

Before (the “dirty” way):

// Bypassing safety with `as any`
const receipt = (payment as any).cardNumber;
// If `payment` is actually a Bank Transfer, this is `undefined`.

After (the “guard” way):

if ("cardNumber" in payment) {
  const receipt = payment.cardNumber; // Safe and verified
}

in 연산자를 사용하면 외부 API에서 들어오는 예측할 수 없는 데이터를 다룰 때 방패 역할을 합니다.

Summary: Your Type Guard Decision Tree

어떤 가드를 사용해야 할지 모르겠나요? 아래 체크리스트를 따라 보세요:

SituationGuard to Use
Simple primitive (string, number, boolean, etc.)typeof
Object created with new MyClass()instanceof
Types share a common property like type or statusDiscriminated Unions (복잡한 로직에 가장 적합)
Checking for a specific property on a “messy” objectin operator
You want a reusable function to clean up if statementsCustom Type Predicate

올바른 가드를 사용하면 결제 프로세서가 충돌할 가능성이 크게 줄어들고, 고객 만족도는 크게 높아집니다.

Predicate (is)

결론

Type Guards는 TypeScript의 엄격한 규칙과 JavaScript의 유연한 현실 사이의 격차를 메워줍니다. 결제 시스템에 이러한 패턴을 구현함으로써 단순히 코드를 작성하는 것이 아니라, 모든 엣지 케이스를 손쉽게 처리할 수 있는 신뢰성 있고 예측 가능한 엔진을 구축하는 것입니다.

코딩 즐겁게 하세요!

참고:
Mastering TypeScript Type Guards — Better Stack Community

0 조회
Back to Blog

관련 글

더 보기 »

JS에서 배열 평탄화 마스터

배열 메서드에 대한 블로그를 쓰는 중에 중첩 배열을 처리하기 위한 Array.flat 메서드를 발견했습니다. ES6 이전에는 배열을 평탄화하는 것이 그리 간단하지 않았습니다 an...

AI 에이전트, 이제 신용카드 보유

오늘 Nevermined는 많은 fintech 및 crypto 개발자들이 기다려온 통합을 발표했습니다: AI 에이전트에게 통합된 commerce layer를 제공하여 both del…

배포를 도박처럼 여기지 마세요

무엇이 바뀌었는지 알려드리겠습니다. 얼마 전, 우리 팀은 성공처럼 보이는 문제를 겪었습니다: 우리는 지속적으로 배포하고 있었습니다. PRs가 매일 병합되고, 기능이 매…