Octoping의 블로그
article thumbnail

타입스크립트를 사용하다보면, 더 정확한 타입 추론을 위해 함수의 파라미터를 string, number 따위 대신 리터럴로 받고 싶어질 때가 있다.

 

굉장히 인공적인 예시이지만 다음 예시를 살펴보자. 

 

타입을 리터럴로 받고 싶은 상황의 예시

function returnTypeofString(name: string) {
    return name;
}

// both types are string 🤯
const res = returnTypeofString("abc");
const res2 = returnTypeofString('John' as const);

나는 아주 강력한 타입 추론을 위해 res의 타입이 'abc'와 같이 나오길 원한다.

하지만 타입은 string으로 나오게 된다.

 

리얼루

 

하지만 이런 상황에서 리터럴로 타입을 받고 싶을 경우 어떻게 할까?

 

 

방법

type StringLiteral<T> = T extends string ? string extends T ? never : T : never;

이런 기가 막힌 방법이 있다 💀

 

와우! 이것으로 타입 추론을 극한까지 올릴 수 있게 되었다.

 

 

다른 타입의 리터럴들

type StringLiteral<T> = T extends string ? string extends T ? never : T : never;
type NumberLiteral<T> = T extends number ? number extends T ? never : T : never;
type BooleanLiteral<T> = T extends boolean ? boolean extends T ? never : T : never;

꼭 string이 아니더라도, number, boolean도 리터럴로 받을 수 있다.

 

type Literal<T> = StringLiteral<T> | NumberLiteral<T> | BooleanLiteral<T>;

이런 식으로 활용할 수도 있겠다?

 

 

응용

사실 이런 이상한 결벽같은 타입추론을 이용할만한 곳은 빌더패턴이나 TypeORM 같이 문자열로 타입을 가져와야하는 곳이 아닐까 한다.

 

빌더 패턴

type StringLiteral<T> = T extends string ? string extends T ? never : T : never;
type NumberLiteral<T> = T extends number ? number extends T ? never : T : never;
type BooleanLiteral<T> = T extends boolean ? boolean extends T ? never : T : never;

type Literal<T> = StringLiteral<T> | NumberLiteral<T> | BooleanLiteral<T>;


class Builder<T extends ReadonlyArray<unknown>> {
  private constructor(private readonly value: T) {}

  public static create() {
    return new Builder([] as const);
  }

  public add<K>(value: Literal<K>): Builder<[...T, K]> {
    return new Builder([...this.value, value]);
  }

  public build(): T {
    return this.value;
  }
}


// 실사용
const result = Builder
    .create()
    .add(1)
    .add("a")
    .add(true)
    .build();

이런 식으로다가 빌더 패턴을 만들어볼 수 있겠다.

 

타입추론 당연히 잘 된다

 

 

문자열로 타입을 가져와야 할 때

class User {
  constructor(
    public id: number,
    public name: string,
    public age: number,
    public isMale: boolean
  ) {}
}

class UserRepository {
  public select<T extends keyof User>(findQuery: {
    column: StringLiteral<T>[];
    where: Partial<User>;
  }): Pick<User, T> {
    return {} as any;
  }
}

이런 기가 막히게 TypeORM스러운 클래스를 만들어보자.

 

select 해오길 원하는 컬럼만 쏙쏙 타입으로 지정 가능하다
실제 User에 없는 컬럼을 select 하면 에러를 발생한다

오! 이렇게 TypeORM은 이루어내지 못했던 제대로 된 타입 추론을 해낼 수 있었다.

profile

Octoping의 블로그

@Octoping

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!