Skip to content
뒤로가기

타입스크립트 핸드북 - Narrowing

게시된 날짜:  at 

목차


Narrowing 인트로 이미지

이 글은 타입스크립트 핸드북 Narrowing를 읽고 스터디 발표를 위해 정리한 글입니다.
스터디원 모두가 해당 내용을 이미 읽었기 때문에, 학습 효과를 높이고자 퀴즈 형식으로 재구성하여 정리했습니다. 감사합니다.


1. Narrowing 기본 개념

Q1. Narrowing은 if/else, 삼항, return 같은 제어 흐름을 따라가며 타입을 더 구체적으로 만든다.

→ O / X 이유도 함께 말씀해주세요






















2. typeof & 타입 가드

Q2. typeof v === "number" 블록 안에서 v의 타입은? 이유도 함께 말씀해주세요

function format(v: string | number) {
  if (typeof v === "number") {
    // 여기서 v의 타입은?
    return v.toFixed(1);
  }
  return v.toUpperCase();
}
  1. string
  2. number
  3. string | number






















3. Truthiness Narrowing (Truthy/Falsy 좁히기)

Q3. if (name)거짓이 되는 값은? (다중 선택) 이유도 함께 말씀해주세요

function greet(name?: string) {
  if (name) return `Hi, ${name}`;

  return "Hi, stranger";
}
  1. "Alice"
  2. ""
  3. undefined
  4. "0"






















4. Equality Narrowing (동등성 좁히기)

Q4. if 블록 안에서 x, y의 타입은? 이유도 함께 말씀해주세요

function same(x: string | number, y: string | boolean) {
  if (x === y) {
    return [x, y]; // 여기서 x, y의 타입은?
  }
}
  1. string
  2. number
  3. boolean
  4. string | number | boolean






















Q4-보너스. value != nullnullundefined 둘 다를 걸러낸다.

→ O / X 이유도 함께 말씀해주세요






















5. in 연산자 Narrowing

Q5. 각 분기에서 a의 타입으로 올바른 것은? 이유도 함께 말씀해주세요

type Cat = { meow: () => void };
type Dog = { bark: () => void };
type Human = { meow?: () => void; bark?: () => void };

function speak(a: Cat | Dog | Human) {
  if ("meow" in a) {
    // 여기서 a의 타입은?
    return "maybe cat or human";
  }
  // 여기서 a의 타입은?
  return "maybe dog or human";
}
  1. 참분기: Cat | Human / 거짓분기: Dog
  2. 참분기: Cat / 거짓분기: Dog | Human
  3. 참분기: Cat | Human / 거짓분기: Dog | Human
  4. 참분기: Cat / 거짓분기: Dog






















6. instanceof Narrowing

Q6. if 블록 안에서 x의 타입은? 이유도 함께 말씀해주세요

function info(x: Date | URL) {
  if (x instanceof URL) {
    // 여기서 x의 타입은?
    return x.hostname;
  }
  return x.toISOString();
}
  1. Date
  2. URL
  3. Date | URL






















7. Assignments Narrowing (할당을 통한 타입 좁히기)

Q7. 각 줄에서 관찰되는 타입으로 맞는 것은? 이유도 함께 말씀해주세요

let v: string | number = Math.random() > 0.5 ? "hi" : 7;
v = 9; // 지금 관찰되는 타입은?
v = "bye"; // 지금 관찰되는 타입은?
  1. 첫 재할당 후 number, 그 다음 string
  2. 둘 다 string | number
  3. 둘 다 any





















Q7-보너스. 마지막 v는 타입 에러가 발생할까? 이유도 함께 말씀해주세요

→ O / X

let v: string | number = Math.random() > 0.5 ? "hi" : 7;
v = 9;
v = "bye";
v = true; // 타입 에러가 발생할까?






















8. Control Flow Analysis (제어 흐름 분석)

Q8. return으로 한 분기가 종료되면, 남은 경로에서는 해당 분기의 타입 가능성은 제거되어 더 좁혀진다. 이에 대한 올바른 설명은?

  1. return은 타입과 관계없고 단순히 함수 실행만 끝낸다.
  2. return으로 분기가 끝나면, 이후 경로에서 해당 타입 가능성은 제거되어 타입이 좁혀진다.
  3. return은 분기와 상관없이 항상 타입을 그대로 유지한다.
  4. return이 나오면 타입은 좁혀지지만, strictNullChecks를 꺼야 동작한다.






















9. Type Predicates (사용자 정의 타입 가드)

Q9. 아래 예제에서 isOk(something)를 반환한 분기 안에서 something의 타입은? 이유도 함께 말씀해주세요

function isOk(v: unknown): v is { ok: true } {
  return typeof v === "object" && v !== null && (v as any).ok === true;
}

const something: unknown = { ok: true };

if (isOk(something)) {
  // 여기서 something 타입은 무엇일까요?
  console.log(typeof something);
} else {
  // 여기서 something 타입은 무엇일까요?
  console.log(typeof something);
}
  1. 번호
    1. if 분기 안: something{ ok: true }
    2. else 분기 안: somethingunknown
  2. 번호
    1. if 분기 안: somethingobject
    2. else 분기 안: somethingnull
  3. 번호
    1. if 분기 안: somethingany
    2. else 분기 안: somethingnever






















10. Discriminated Unions (식별 가능한 유니언 타입)

Q10. 아래 login 함수가 각 분기에서 안전한 이유는 무엇일까요?

type Login =
  | { type: "password"; id: string; pw: string }
  | { type: "oauth"; provider: "google" | "github"; token: string };

function login(l: Login) {
  switch (l.type) {
    case "password":
      return l.pw.length;
    case "oauth":
      return l.provider.toUpperCase();
  }
}
  1. switch에서 각 케이스에 return이 있어서
  2. strictNullChecks 옵션 설정 덕분에
  3. 공통 리터럴 필드로 각 케이스의 필수 프로퍼티가 보장되니까
  4. any 타입으로 암묵 변환되기 때문에





















11. never & Exhaustiveness Checking (never 타입 & 빠짐없이 검사하기)

Q11. default의 역할은?

type Animal = { kind: "cat"; meow: string } | { kind: "dog"; bark: string };

function speak(a: Animal) {
  switch (a.kind) {
    case "cat":
      return a.meow;
    case "dog":
      return a.bark;
    default:
      const _ex: never = a;
      return _ex;
  }
}
  1. 아무 의미 없음
  2. 새 케이스 추가 시 누락을 컴파일 타임 에러로 잡아줌
  3. 런타임 최적화






















12. 보너스 (실전 설계 문제)

Q12. 다음 옵션 설계를 **Discriminated Union(식별 가능한 유니언 타입)**으로 안전하게 바꿔주세요

interface Pay {
  method: "card" | "cash";
  cardNo?: string;
  amount?: number;
}

// method가 "card"인데 cardNo가 없어도 통과됨 → 타입에서 걸러지지 않음
const card: Pay = { method: "card" };





















  1. 정답

    type Pay =
      | { method: "card"; cardNo: string }
      | { method: "cash"; amount: number };

  1. 정답
interface CardPay {
  method: "card";
  cardNo: string;
  amount: number;
}

interface CashPay {
  method: "cash";
  amount: number;
}

type Pay = CardPay | CashPay;

수정 제안하기

이전 게시글
자바스크립트에서 긴 작업을 분할하는 다양한 방법
다음 게시글
테스트는 어떻게 좋은 코드를 만드는가(feat. 험블 객체 패턴)