icon안동민 개발노트

유니온과 인터섹션 타입


 타입스크립트의 유니온과 인터섹션 타입은 복잡한 타입을 표현하고 조합하는 강력한 도구입니다.

 이 절에서는 이 두 가지 타입의 개념, 사용법, 그리고 고급 기법들을 살펴보겠습니다.

유니온 타입

 유니온 타입은 여러 타입 중 하나일 수 있는 값을 표현합니다.

type StringOrNumber = string | number;
 
function printId(id: StringOrNumber) {
    console.log("Your ID is: " + id);
}
 
printId(101);  // 정상
printId("202");  // 정상
printId(true);  // 에러

 사용 시나리오

  • 함수가 여러 타입의 인자를 받을 수 있을 때
  • API 응답이 여러 형태일 수 있을 때
  • 변수가 여러 타입의 값을 가질 수 있을 때

인터섹션 타입

 인터섹션 타입은 여러 타입을 결합합니다.

interface Colorful {
    color: string;
}
 
interface Circle {
    radius: number;
}
 
type ColorfulCircle = Colorful & Circle;
 
let cc: ColorfulCircle = {
    color: "red",
    radius: 42
};

 유니온 vs 인터섹션

  • 유니온 (|) : "또는"
  • 인터섹션 (&) : "그리고"

타입 가드와 타입 좁히기

 타입 가드는 유니온 타입의 값을 더 구체적인 타입으로 좁히는 데 사용됩니다.

function padLeft(value: string, padding: string | number) {
    if (typeof padding === "number") {
        return " ".repeat(padding) + value;
    }
    if (typeof padding === "string") {
        return padding + value;
    }
    throw new Error(`Expected string or number, got '${padding}'.`);
}

 타입 가드 방법

  • typeof
  • instanceof
  • in 연산자
  • 사용자 정의 타입 가드 함수

식별 가능한 유니온

 식별 가능한 유니온은 공통 필드를 사용하여 유니온 멤버를 구분합니다.

interface Square {
    kind: "square";
    size: number;
}
 
interface Rectangle {
    kind: "rectangle";
    width: number;
    height: number;
}
 
type Shape = Square | Rectangle;
 
function area(s: Shape) {
    switch (s.kind) {
        case "square": return s.size * s.size;
        case "rectangle": return s.width * s.height;
    }
}

 장점

  • 타입 안전성 강화
  • 코드의 가독성과 유지보수성 향상

인터섹션 타입의 조합

 인터섹션 타입을 사용하여 여러 타입을 결합할 수 있습니다.

type Admin = {
    name: string;
    privileges: string[];
};
 
type Employee = {
    name: string;
    startDate: Date;
};
 
type AdminEmployee = Admin & Employee;
 
const ae: AdminEmployee = {
    name: "John",
    privileges: ["server-access"],
    startDate: new Date()
};

 장점

  • 코드 재사용성 향상
  • 복잡한 타입을 모듈화하여 관리 가능

 단점

  • 과도한 사용 시 타입이 복잡해질 수 있음

분산 법칙

 유니온과 인터섹션 타입은 분산 법칙을 따릅니다.

type A = { a: number };
type B = { b: string };
type C = { c: boolean };
 
type ABC = A & (B | C);
// 동일: (A & B) | (A & C)

 이 특성은 복잡한 타입 연산에서 중요한 역할을 합니다.

never 타입과 유니온

 never 타입은 유니온에서 특별한 역할을 합니다.

type NonNullable<T> = T extends null | undefined ? never : T;
 
type Result = NonNullable<string | null | undefined>;
// Result는 string 타입

 never는 유니온에서 자동으로 제거되어, 타입 변환과 필터링에 유용합니다.

복잡한 타입 모델링

 유니온과 인터섹션을 조합하여 복잡한 타입을 모델링할 수 있습니다.

type JsonPrimitive = string | number | boolean | null;
type JsonObject = { [key: string]: JsonValue };
type JsonArray = JsonValue[];
type JsonValue = JsonPrimitive | JsonObject | JsonArray;
 
function isJsonValue(value: unknown): value is JsonValue {
    if (value === null) return true;
    switch (typeof value) {
        case "string":
        case "number":
        case "boolean":
            return true;
        case "object":
            if (Array.isArray(value)) return value.every(isJsonValue);
            if (value !== null) {
                return Object.values(value).every(isJsonValue);
            }
            return false;
        default:
            return false;
    }
}

 이 예제는 JSON 값의 타입을 정확하게 모델링하고 있습니다.

흔한 오류와 해결 방법

 1. 타입 좁히기 실패

function process(value: string | number) {
    if (typeof value === "string") {
        console.log(value.toUpperCase());
    } else {
        console.log(value.toFixed(2));
    }
}

 해결 : 명시적인 타입 가드 사용

 2. 인터섹션 타입에서의 프로퍼티 충돌

type A = { a: number, c: string };
type B = { b: number, c: number };
type AB = A & B;  // c의 타입은?

 해결 : 인터페이스 설계 시 주의, 필요한 경우 타입 단언 사용

설계 원칙과 Best Practices

  1. 유니온 타입 사용 시 철저한 타입 체크 수행
  2. 식별 가능한 유니온 패턴 적극 활용
  3. 인터섹션 타입 사용 시 프로퍼티 충돌 주의
  4. 복잡한 타입은 작은 단위로 분해하여 조합
  5. 타입 별칭을 활용하여 재사용성과 가독성 향상
  6. never 타입을 활용한 타입 연산 및 오류 검출
  7. 제네릭과 조건부 타입을 결합하여 유연한 타입 설계

 유니온과 인터섹션 타입은 타입스크립트에서 복잡한 타입을 표현하고 조합하는 강력한 도구입니다. 유니온 타입은 여러 타입 중 하나를 나타내는 "또는" 관계를 표현하며, 인터섹션 타입은 여러 타입을 결합하는 "그리고" 관계를 나타냅니다.

 유니온 타입은 함수가 여러 타입의 인자를 받을 수 있거나, 변수가 여러 타입의 값을 가질 수 있는 상황에서 특히 유용합니다. 이를 효과적으로 사용하기 위해서는 타입 가드를 통한 타입 좁히기가 필수적입니다. typeof, instanceof, in 연산자, 그리고 사용자 정의 타입 가드 함수 등을 활용하여 구체적인 타입으로 좁혀 나갈 수 있습니다.

 식별 가능한 유니온 패턴은 유니온 타입의 각 멤버를 구분할 수 있는 공통 필드를 사용하여 타입 안전성을 강화하는 기법입니다. 이 패턴을 사용하면 컴파일러가 타입을 정확히 추론할 수 있어, 런타임 오류를 줄이고 코드의 가독성을 높일 수 있습니다.

 인터섹션 타입은 여러 타입의 특성을 모두 갖춘 새로운 타입을 생성합니다. 이는 기존 타입들을 조합하여 복잡한 객체 타입을 만들 때 유용합니다. 하지만 과도한 사용은 타입의 복잡성을 증가시킬 수 있으므로 주의가 필요합니다.

 유니온과 인터섹션 타입의 분산 법칙을 이해하는 것은 복잡한 타입 연산을 다룰 때 중요합니다. 이 법칙은 타입 시스템이 어떻게 복합 타입을 해석하고 처리하는지를 설명합니다.

 never 타입과 유니온 타입의 관계를 이해하고 활용하면, 타입 필터링이나 조건부 타입 구현 등 고급 타입 연산을 수행할 수 있습니다. never 타입이 유니온에서 자동으로 제거되는 특성은 타입 변환과 필터링에 특히 유용합니다.

 유니온과 인터섹션 타입을 함께 사용하여 복잡한 데이터 구조를 모델링할 수 있습니다. JSON 값의 타입을 정의하는 예시에서 볼 수 있듯이, 이러한 조합은 실제 세계의 복잡한 데이터 구조를 정확하게 표현하는 데 도움이 됩니다.

 이러한 고급 타입들을 사용할 때는 몇 가지 흔한 오류에 주의해야 합니다. 타입 좁히기 실패나 인터섹션 타입에서의 프로퍼티 충돌 등의 문제는 적절한 타입 가드 사용과 인터페이스 설계 시의 주의로 해결할 수 있습니다.

 효과적인 유니온과 인터섹션 타입 사용을 위해서는 몇 가지 설계 원칙과 Best Practices를 따르는 것이 좋습니다. 철저한 타입 체크, 식별 가능한 유니온 패턴 활용, 프로퍼티 충돌 주의, 복잡한 타입의 모듈화, 타입 별칭 활용, never 타입을 이용한 오류 검출, 그리고 제네릭과 조건부 타입의 결합 등이 여기에 포함됩니다.

 결론적으로, 유니온과 인터섹션 타입은 타입스크립트의 타입 시스템을 더욱 표현력 있고 유연하게 만드는 핵심 기능입니다. 이들을 적절히 활용하면 복잡한 비즈니스 로직을 정확하게 모델링하고, 타입 안전성이 높은 코드를 작성할 수 있습니다. 그러나 이러한 고급 기능들은 신중하게 사용해야 하며, 코드의 복잡성과 가독성 사이의 균형을 유지하는 것이 중요합니다.