StudyForYou / ouahhan-typescript-with-react

우아한 타입스크립트 with 리액트 스터디 레포 🧵
4 stars 0 forks source link

#13 [4장_3] 식별할 수 있는 유니온을 사용하는 이유에 대해 설명해주세요! #25

Closed hyeyoonS closed 2 months ago

hyeyoonS commented 3 months ago

❓문제

식별할 수 있는 유니온을 사용하는 이유에 대해 설명해주세요!

🎯답변 요약

🎯답변

interface Circle {
    figureType: 'circle';
    radius: number;
}

interface Rectangle {
    figureType: 'rectangle';
    width: number;
    height: number;
}

interface Triangle {
    figureType: 'triangle';
    width: number;
    height: number;
}

type Shape = Circle | Rectangle | Triangle;
type AreaFunc = (shape: Shape) => number;

const area: AreaFunc = (shape) => {
    switch (shape.figureType) {
        case 'circle':
            return 3.14 * shape.radius ** 2;
        case 'rectangle':
            return shape.width * shape.height;
        case 'triangle':
            return Math.floor((shape.width * shape.height) / 2);
    }
}

ps. switch case 너무 조아..!

drizzle96 commented 3 months ago

식별할 수 있는 유니온은 비슷한 구조를 가진 각 타입이 서로 호환되지 않도록 식별할 수 있는 유니온 필드를 추가하는 방법입니다. 식별할 수 있는 유니온이 필요한 이유는 타입스크립트가 구조적 타이핑을 따르기 때문입니다.

type TextError = {
  errorCode: string;
  errorMessage: string;
}

type ToastError = {
  errorCode: string;
  errorMessage: string;
  toastShowDuration: number;
}

type AlertError = {
  errorCode: string;
  errorMessage: string;
  onConfirm: () => void;
}

type ErrorFeedbackType = TextError | ToastError | AlertError;
const wrongError: ErrorFeedbackType = {
  errorCode: '999',
  errorMessage: 'wrong error',
  toastShowDuration: 3000,
  onConfirm: () => {}
}

구조적 타이핑에서는 객체가 타입에 정의된 모든 속성을 가지고 있으면 타입과 호환되는데 추가적인 필드를 포함하는 것은 상관하지 않습니다. 따라서 wrongError 객체는 ToastError와 AlertError의 필드를 동시에 갖고 있어 타입 에러가 나야하지만 에러가 발생하지 않습니다. 이는 원치 않는 상황으로 에러가 발생하도록 만들어야 합니다.

type TextError = {
  errorType: 'TEXT',
  errorCode: string;
  errorMessage: string;
}

type ToastError = {
  errorType: 'TOAST',
  errorCode: string;
  errorMessage: string;
  toastShowDuration: number;
}

type AlertError = {
  errorType: 'ALERT',
  errorCode: string;
  errorMessage: string;
  onConfirm: () => void;
}

type ErrorFeedbackType = TextError | ToastError | AlertError;

// 🚨 원하는 대로 Type Error 발생
const wrongError: ErrorFeedbackType = {
  errorCode: '999',
  errorMessage: 'wrong error',
  toastShowDuration: 3000,
  onConfirm: () => {}
}

위와 같이 식별할 수 있는 유니온(errorType)을 추가하면 각 타입이 서로 구분되는 값의 필드를 갖기 때문에 객체가 여러 타입에 동시에 포함될 수 없게 됩니다. 그래서 기대하는 대로 정확하지 않은 에러 객체에 대해 타입 에러를 발생시킬 수 있습니다.

qooktree1 commented 3 months ago
interface Circle {
    figureType: 'circle';
    radius: number;
}

interface Rectangle {
    figureType: 'rectangle';
    width: number;
    height: number;
}

interface Triangle {
    figureType: 'triangle';
    width: number;
    height: number;
}

type Shape = Circle | Rectangle | Triangle;
type AreaFunc = (shape: Shape) => number;

const area: AreaFunc = (shape) => {
    switch (shape.figureType) {
        case 'circle':
            return 3.14 * shape.radius ** 2;
        case 'rectangle':
            return shape.width * shape.height;
        case 'triangle':
            return Math.floor((shape.width * shape.height) / 2);
    }
}

ps. switch case 너무 조아..!

summerkimm commented 3 months ago
  1. 컴파일 타임에 타입 검사를 통해 안전성을 확보할 수 있음. 이는 런타임 에러를 줄이고 코드의 신뢰성을 높임
  2. 타입에 따른 분기 처리가 명확하게 작성되므로 유지보수가 용이해짐
  3. 자동 완성 기능과 타입 추론 기능을 최대한 활용할 수 있어 개발 생산성을 크게 향상시킴
hyeyoonS commented 3 months ago

→ 타입 간의 구조 호환을 막기 위해 타입마다 구분할 수 있는 판별자를 달아주어 포함 관계를 제거하면 더욱 안정적인 코드를 작성할 수 있다.

type TextError = {
    errorCode: string;
    errorMessage: string;
};
type ToastError = {
    errorCode: string;
    errorMessage: string;
    toastShowDuration: number; 
};
type AlertError = {
    errorCode: string;
    errorMessage: string;
    onConfirm: () => void;
};

type ErrorFeedbackType = TextError | ToastError | AlertError;

/* 정상적인 사용 */ 
const errorArr: ErrorFeedbackType[] = [
    {errorCode: "100", errorMessage: "텍스트 에러"},
    {errorCode: "200", errorMessage: "토스트 에러", toastShowDuration: 3000},
    {errorCode: "300", errorMessage: "얼럿 에러", onConfirm: () => {} },
];
/* 비정상적인 사용 */ 
const errorArr: ErrorFeedbackType[] = [
    {errorCode: "999", errorMessage: "잘못된 예시", toastShowDuration: 3000, onConfirm: () => {} },
];
type A = { a: number };
type B = { b: string };
type C = A | B;
const obj: C = { a: 1, b: "hello" };

⇒ 이 때 객체 obj는 A와 B의 모든 프로퍼티를 가지고 있는바, C타입은 올바른 타입으로 간주하는 문제가 있다.