typescript - enum 값에 따라 함수 타입 설정하기

ㅎㄱㅎ·2022년 4월 5일
0
post-thumbnail
const Test = (type: Enum, func: any) => { ... }

일 때 Enum 값에 따라 func의 타입이 달라지는 경우가 있다. 물론 작업자가 나 혼자면 any로 써도 큰 문제는 없겠으나 여러사람과 협업할 때는 func의 타입을 부여하는게 훨씬 좋지 않을까? 

export abstract class Hookable<G> {
  protected _handlers: Map<any, any[]> = new Map();
  
  public connect<T extends keyof G>(
    type: T,
    ...options: 여기를 어떻게 해야 할까?
  ): void {
    if (!this._handlers.has(type)) {
      this._handlers.set(type, []);
    }
    options.forEach((f: G[T]) => this._handlers.get(type)?.push(f));
  }
  
  public disconnect<T extends keyof G>(
    type: T,
    ...options: 여기를 어떻게 해야 할까?
  ): void {
    const funcs = this._handlers.get(type);
    if (funcs) {
      options.forEach((g: G[T]) => {
        funcs.filter((f: G[T]) => f === g);
      });
    }
  }
  
  protected abstract _emitHook<T extends keyof G>(
    type: T,
    sender?: any,
    args?: any
  ): void;
  
  public disconnectAll(): void {
    this._handlers.clear();
  }
}

위 클래스를 상속받아서 어떤 클래스를 구현하면 그 클래스는 Hookable 하게 된다. ( pub/sub ) 

여기서 핵심적으로 생각해 봐야 할 부분은 connect, disconnect 함수에 어떻게 타입을 지정하느냐다.

export type ConditionalOptions<T, K extends keyof T> = T[K] extends null
  ? []
  : [T[K]];

위의 타입을 보자. 제네릭이라 좀 복잡할수도 있는데 하나씩 뜯어보면 심플하다. 말로 풀어서 보면

K는 T의 key이고 T[k](인덱스 타입)이 null 이면 [] null이 아니면 [T[K]] 

이다. 이 인터페이스를 options의 타입으로 지정하면 된다. 이제 왜 ...options인지 받아들일 수 있다.

export abstract class Hookable<G> {
  protected _handlers: Map<any, any[]> = new Map();
  
  public connect<T extends keyof G>(
    type: T,
    ...options: ConditionalOptions<G, T>
  ): void {
    if (!this._handlers.has(type)) {
      this._handlers.set(type, []);
    }
    options.forEach((f: G[T]) => this._handlers.get(type)?.push(f));
  }
  
  public disconnect<T extends keyof G>(
    type: T,
    ...options: ConditionalOptions<G, T>
  ): void {
    const funcs = this._handlers.get(type);
    if (funcs) {
      options.forEach((g: G[T]) => {
        funcs.filter((f: G[T]) => f === g);
      });
    }
  }
  
  protected abstract _emitHook<T extends keyof G>(
    type: T,
    sender?: any,
    args?: any
  ): void;
  
  public disconnectAll(): void {
    this._handlers.clear();
  }
}

그럼 이제 G는 뭐고 T는 뭐로 해주면 좋겠느냐 만 남았다. 아래 코드를 보자

export enum HookType {
	INITIALIZE = 'initialize',
    TEST = 'test'
}

export type initializeHookFunction = (data: any) => void;
export type testHookFunction = (data: any) => number;

export interface PossibleValues {
	initialize: initializeHookFunction;
    test: testHookFunction;
}

PossibleValues가 G 이고 HookType의 enum 값들이 T인 것을 알 수 있다.(반드시 PossibleValues에 포함된 키만을 허용한다) 이 두개를 가지고 PossibleValues interface에서 인덱스 타입을 가져와 ~HookFunction을 추천해 준다. 

생각보다 이런 케이스가 더러 있으니 알아두면 좋을 것 같다.

profile
dog발자

0개의 댓글