[Typescript] 타입 unkown vs never와 as vs is 이해하기

최소희·2024년 8월 18일
0

프론트엔드 학습

목록 보기
17/23

unknown vs never

TS Playground - An online editor for exploring TypeScript and JavaScript

1. unknown 타입

  • 정의: unknownany와 유사하지만 더 안전한 타입으로, 타입이 확정되기 전까지는 어떤 조작도 허용되지 않는 타입이다.

  • 사용 용도: 주로 API 호출이나 JSON 파서를 감싸는 것과 같이 동적 데이터의 타입을 처리할 때 사용되며, 타입 안전성을 유지하는 데 유리하다.

  • 모호함과 세부사항의 차이: any 타입은 어떤 타입이든 허용하지만, unknown 타입은 해당 값을 사용하기 전에 그 타입을 확정해야 한다.

  • 예시:

    1️⃣ JSON 파서

    const jsonParser = (jsonString: string) => JSON.parse(jsonString);
    const myAccount = jsonParser(`{ "name": "Dorothea" }`);
    myAccount.name;  // 정상 동작 (any 타입)
    myAccount.email; // 정상 동작 (any 타입)
    
    const jsonParserUnknown = (jsonString: string): unknown => JSON.parse(jsonString);
    const myOtherAccount = jsonParserUnknown(`{ "name": "Samuel" }`);
    myOtherAccount.name;  // 오류 발생 (unknown 타입)
    
    • unknown 타입을 반환하는 jsonParserUnknown은 사용자가 적절한 타입 선언을 하지 않는 한 사용할 수 없다. API를 사용하는 사람이 타입을 정의하도록 강제한다.
  • Unkown Type 좁히기

    1️⃣ typeof, instanceof:

    function stringifyForLogging(value: unknown): string {
      if (typeof value === "function") {
        // Within this branch, `value` has type `Function`,
        // so we can access the function's `name` property
        const functionName = value.name || "(anonymous)";
        return `[function ${functionName}]`;
      }
    
      if (value instanceof Date) {
        // Within this branch, `value` has type `Date`,
        // so we can call the `toISOString` method
        return value.toISOString();
      }
    
      return String(value);
    }

    2️⃣ 타입 확정:

    type User = { name: string };
    const myUserAccount = jsonParserUnknown(`{ "name": "Samuel" }`) as User;
    console.log(myUserAccount.name);  // "Samuel" 정상 동작

    3️⃣ 타입 판별자:

    /**
     * A custom type guard function that determines whether
     * `value` is an array that only contains numbers.
     */
    function isNumberArray(value: unknown): value is number[] {
      return (
        Array.isArray(value) && value.every(element => typeof element === "number")
      );
    }
    
    const unknownValue: unknown = [15, 23, 8, 4, 42, 16];
    
    if (isNumberArray(unknownValue)) {
      // Within this branch, `unknownValue` has type `number[]`,
      // so we can spread the numbers as arguments to `Math.max`
      const max = Math.max(...unknownValue);
      console.log(max);
    }

2. never 타입

  • 정의: never는 코드가 절대 실행되지 않음을 나타내는 타입으로, 예외를 던지거나 불가능한 상황을 표현할 때 사용된다.
  • 사용 용도: 불가능한 코드 경로나 switch 문의 완전성을 보장하기 위해 사용된다.
  • 불가능한 코드 경로: never 타입은 절대로 발생할 수 없는 상황을 표현할 때 사용된다. 예를 들어, 아래 함수는 항상 예외를 던지고 절대 값을 반환하지 않기 때문에 never 타입을 반환한다.
    const neverReturns = (): never => {
      throw new Error("Always throws, never returns");
    };
  • Switch 문에서의 완전성 체크: never 타입은 switch 문에서 모든 가능한 경우를 처리했는지 보장하는 데 유용하다. 예를 들어, enum의 모든 값을 처리해야 할 때 never 타입을 사용하여 누락된 케이스를 감지할 수 있다.
    enum Flower {
      Rose,
      Rhododendron,
      Violet,
      Daisy,
      Tulip?  // 새로운 값 추가
    }
    
    const flowerLatinName = (flower: Flower) => {
      switch (flower) {
        case Flower.Rose:
          return "Rosa rubiginosa";
        case Flower.Rhododendron:
          return "Rhododendron ferrugineum";
        case Flower.Violet:
          return "Viola reichenbachiana";
        case Flower.Daisy:
          return "Bellis perennis";
        default:
          const _exhaustiveCheck: never = flower;
          return _exhaustiveCheck;  
          // Type 'Flower' is not assignable to type 'never'.(2322)
    
      }
    };
  • 교차 타입에서의 never: never 타입은 교차 타입에서 자동으로 제거된다. never는 어떤 값도 가질 수 없기 때문에 never가 실질적으로 타입에 영향을 미치지 않기 때문이다. 따라서 NeverIsRemoved는 결국 string | number 타입으로 간주된다.
    type NeverIsRemoved = string | never | number;
    // NeverIsRemoved는 string | number 타입과 동일

is vs as

is: Type Predicates (타입 판별자)

  • 정의: is는 사용자 정의 타입 가드를 정의할 때 사용하는 키워드로, 특정 조건이 참일 경우 변수의 타입을 좁혀준다.

  • 사용 용도: 함수 내에서 타입을 좁혀 특정 조건에서 타입을 확정할 때 사용된다.

  • 예시:

    1️⃣

    function isFish(pet: Fish | Bird): pet is Fish {
      return (pet as Fish).swim !== undefined;
    }
    
    if (isFish(pet)) {
      pet.swim();  // 여기서 pet은 Fish로 좁혀짐
    } else {
      pet.fly();   // 여기서 pet은 Bird로 좁혀짐
    }

    2️⃣

    /**
     * A custom type guard function that determines whether
     * `value` is an array that only contains numbers.
     */
    function isNumberArray(value: unknown): value is number[] {
      return (
        Array.isArray(value) && value.every(element => typeof element === "number")
      );
    }
    
    const unknownValue: unknown = [15, 23, 8, 4, 42, 16];
    
    if (isNumberArray(unknownValue)) {
      // Within this branch, `unknownValue` has type `number[]`,
      // so we can spread the numbers as arguments to `Math.max`
      const max = Math.max(...unknownValue);
      console.log(max);
    }
  • 주의 사항:

    • 타입을 안전하게 좁히는 데 유용하며, 복잡한 타입 처리를 더 쉽게 할 수 있다.

as: Type Assertions

TS Playground - An online editor for exploring TypeScript and JavaScript

  • 정의: as는 특정 값의 타입을 개발자가 명시적으로 지정하는 방법이다.
  • 사용 용도: TypeScript가 타입을 올바르게 추론하지 못할 때 사용하며, 컴파일러에게 특정 타입으로 간주하도록 지시한다.
  • 예시:
    const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;
  • 주의 사항:
    • 타입 단언은 컴파일 시 제거되며, 런타임에서 타입 검사가 수행되지 않는다.
    • 타입 간의 강제 변환이 가능하지만, 잘못 사용하면 타입 안전성을 해칠 수 있다.

주요 차이점

  • as:
    • 타입을 명시적으로 지정.
    • 런타임 검사가 없으며, 잘못된 타입 지정이 발생할 수 있음.
    • 타입 간 변환을 강제할 때 사용.
  • is:
    • 특정 조건에서 타입을 좁혀주는 역할.
    • 타입 안전성을 보장하며, 조건부 타입 처리에 유용.

코드 출처 및 참고 자료

Documentaion - type-assertions

Documentation - Narrowing

The unknown Type in TypeScript

profile
프론트엔드 개발자 👩🏻‍💻

0개의 댓글