ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Typescript - Assertion Function
    Language/Typescript 2023. 5. 24. 22:59

    유저가 존재하는지에 대한 여부를 확인하는 로직이 아래와 같다고 가정해봅시다.

    interface User {
      readonly id: number;
      readonly name: string;
      readonly email: string;
    }
    
    declare const user: User | null;
    
    function validateNotNullUser(user: User | null) {
      if (!user) {
        throw new Error('유저가 존재하지 않습니다');
      }
    }
    
    validateNotNullUser(user);
    user; // User | null

     

     

    validateNotNullUser 함수는 유저가 존재하지 않을 경우 에러를 발생시킵니다. validateNotNullUser 함수 실행 후에 user 변수의 타입은 어떻게 되어야할까요? 당연히 User 일 것이라고 예상됩니다. 하지만 실제로 확인해보면 여전히 user 변수의 타입은 User | null 인 것을 확인할 수 있습니다.

    만약 함수를 사용하지 않고 바로 유저 존재여부를 확인한다면 위와 같은 문제가 발생하지 않습니다.

     

    if (!user) {
      throw new Error('유저가 존재하지 않습니다');
    }
    user; // User

     

    하지만 유저 존재여부를 체크하는 로직 등은 중복되는 경우가 빈번하여 재사용될 가능성이 높기 때문에 별도의 함수로 나누는 것이 좋다고 생각했습니다.

    타입스크립트에서는 User-Defined Type Guards 라는 개념이 있습니다. “어떤 인자명은 어떠한 타입이다” 라는 값을 반환하는 함수입니다. 하지만 위의 문제는 유저가 존재하지 않을 경우 값을 반환하는 것이 아니라 에러를 발생시키고 있기 때문에 User-Defined Type Guards 는 활용할 수 없을 것 같습니다.

    그렇다면 어떻게 위 문제를 해결할 수 있을까요? 타입스크립트에서는 함수의 매개변수가 어떤 타입인지를 보장할 수 있도록 해주는 방법이 있다고 합니다. 함수의 반환 타입에 asserts <parameter> is <type> 를 추가해주면 됩니다.

     

    interface User {
      readonly id: number;
      readonly name: string;
      readonly email: string;
    }
    
    declare const user: User | null;
    
    function validateNotNullUser(user: User | null): asserts user is User {
      if (!user) {
        throw new Error('유저가 존재하지 않습니다');
      }
    }
    
    validateNotNullUser(user);
    user; // User

     

    추가 후 validateNotNullUser 함수를 실행하면 user 변수의 타입이 User 가 된 것을 확인할 수 있습니다. is 키워드를 제거하고 사용할 수도 있습니다. is 키워드를 제거한다는 것은 매개변수로 전달되는 모든 것들이 참이어야 한다고 정의하는 것이라고 합니다.

     

    function validateNotNullUser(user: User | null): asserts user {
      if (!user) {
        throw new Error('유저가 존재하지 않습니다');
      }
    }
    
    validateNotNullUser(user);
    user; // User

     

    문제는 해결됐지만 한가지 걱정되는 점이 있습니다. asserts <parameter> is <type> 은 결국 타입 단언과 같은 기능을 합니다. 아래 예제들은 실제로 예상되는 로직과 타입이 일치하지 않고 있습니다.

     

    // EX1
    function validateNotNullUser(user: User | null): asserts user is null {
      if (!user) {
        throw new Error('유저가 존재하지 않습니다');
      }
    }
    
    validateNotNullUser(user);
    user; // type - null(O), User(X)
    
    // EX2
    function validateNotNullUser(user: User | null): asserts user {
      if (user) {
        throw new Error('유저가 존재합니다');
      }
    }
    
    validateNotNullUser(user);
    user; // type - User(O), null(X)

    하지만 이러한 문제를 고려하더라도 위와 같은 방법으로 중복되는 로직들을 처리하여 재사용성을 높일 수 있기 때문에 개인적으로 단점보다는 장점이 더 크다는 생각이 듭니다.

     

    참고문서

    https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#assertion-functions

    https://blog.logrocket.com/assertion-functions-typescript/

Designed by Tistory.