상용구 없이 동일한 TypeScript 열거형 간의 매핑

9347 단어 typescript
TypeScript의 열거형은 이상합니다. 구조적/덕 타이핑 패턴을 따르지 않는 TS(내가 아는 한)의 유일한 부분입니다. 문자열 공용체 대신 열거형을 선호한다면 다음과 같은 상황을 경험했을 것입니다.



동일한 구성원을 포함하는 두 개의 열거형이 있지만 TS는 이들이 호환되는지 인식하지 못합니다. 이제 저는 사람들이 문제를 해결하기 위해 활용하는 세 가지 주요 패턴을 봅니다.

  • IDC 접근 방식: 어디서나 캐스팅합니다. 이것은 가장 쉽지만 enum 캐스트를 사용하면 거의 모든 작업을 수행할 수 있으므로 위험합니다.

  • 순수주의자: "그들은 다른 것을 나타내기"때문에 유형이 다르므로 유사성을 무시하고 매퍼 함수(또는 개체)를 만들고 필요한 경우 사용합니다.
    이것은 확실히 매우 안전하지만 많은 유지 관리 오버헤드와 코드(번들) 크기를 추가합니다.

  • 중간 지점: 함수를 생성하지만 내부에 캐스트합니다.
    이것은 한 번만 캐스트하기 때문에 종이에 접근하는 것보다 더 좋아 보이지만 실제로는 여전히 동일한 위험에 노출되기 쉽습니다. 두 개의 열거형이 어떤 지점에서든 분기되면 TS에서 단일 경고를 받지 않습니다.

  • 상용구가 없는 유형 안전



    #3의 순도와 번들 크기를 유지하면서 접근 방식 #2의 유형 안전성을 어떻게든 얻을 수 있을까? 우리는 할 수 있습니다.

    JS 수준에서 이 세 번째 접근 방식은 사소하며 매퍼 기능은 기본적으로 x => x 입니다.

    따라서 이제 동일한 열거형 사이에서만 매핑할 수 있도록 TS 제약 조건을 추가하는 것이 "단지"문제입니다.

    구현



    핵심



    내 현재 솔루션은 단순성을 위해 대칭(동일한 키 및 값) 열거형으로만 작동하지만 매우 잘 확장될 수 있습니다. 먼저 대칭 열거형이 무엇인지 정의해 보겠습니다.

    type SymmetricalEnum<TEnum> = {
      [key in keyof TEnum]: key;
    };
    


    그런 다음 트릭이 있습니다. 우리는 매핑의 결과 값이 무엇인지 정의할 수 있습니다. 이것은 기본적으로 모든 이점과 함께 TS 메타 언어 수준에서 매핑을 수행하는 것입니다.

    type MapperResult<
      TSourceEnumObj,
      TDestEnumObj extends SymmetricalEnum<TSourceEnumObj>,
      TSourceValue extends keyof TSourceEnumObj
    > = TDestEnumObj extends { [key in TSourceValue]: infer TResult } ? TResult : never;
    


    3개의 일반 인수가 있습니다.
  • 무엇이든 될 수 있는 소스 열거 유형입니다(원하는 경우 여기에 제약 조건을 추가할 수 있음).
  • 소스 열거형을 확장해야 하는 대상 열거형 유형입니다. 이것이 형식 안전 논리의 핵심입니다. 소스의 상위 집합이 아닌 열거형에 매핑하려고 하면 이 줄이 여기에서 소리를 냅니다.
  • 매퍼 함수에 지정된 실제 입력 값의 유형입니다. 그런 다음 조건문에서 기본적으로 대상 유형에서 해당 값을 "풀"하는 데 사용됩니다. 조건부 유형을 읽어야 하는 경우 the documentation부터 시작하는 것이 좋습니다.

  • 유형 수준에서 매핑이 필요한 경우 위의 내용을 자체적으로 사용할 수 있습니다. 예를 들어 이 마법을 참조하세요.



    호환되지 않는 열거형은 거부됩니다. 다음과 같은 경우에도 매우 명확한 오류 메시지가 표시됩니다.

    매퍼 함수 만들기



    이제 이 마법을 활용하여 매퍼 함수를 ​​자동 생성하는 고차 함수를 만들 준비가 되었습니다.

    const createEnumMapperFunction =
      <TSourceEnumObj, TDestEnumObj extends SymmetricalEnum<TSourceEnumObj>>(from: TSourceEnumObj, to: TDestEnumObj) =>
      <TInput extends keyof TSourceEnumObj>(value: TInput) =>
        value as MapperResult<TSourceEnumObj, TDestEnumObj, TInput>;
    


    포착한 오류를 포함하여 여기에서 작동 중인 것을 볼 수 있습니다.



    그리고 이것이 다입니다! 이제 모든 switch 문을 createEnumMapperFunction 에 대한 호출로 바꾸거나 캐스트를 위와 같은 유형 안전 버전으로 바꿀 수 있습니다. 비대칭 열거형 또는 기타 사례에 대한 지원을 추가하는 것과 같이 필요에 따라 자유롭게 조정할 수 있습니다.

    최종 코드



    StackOverflow 키보드가 있는 경우

    전체 코드는 다음과 같습니다TS Playground with examples.

    type SymmetricalEnum<TEnum> = {
      [key in keyof TEnum]: key;
    };
    
    type MapperResult<
      TSourceEnumObj,
      TDestEnumObj extends SymmetricalEnum<TSourceEnumObj>,
      TSourceValue extends keyof TSourceEnumObj
    > = TDestEnumObj extends { [key in TSourceValue]: infer TResult } ? TResult : never;
    
    const createEnumMapperFunction =
      <TSourceEnumObj, TDestEnumObj extends SymmetricalEnum<TSourceEnumObj>>(from: TSourceEnumObj, to: TDestEnumObj) =>
      <TInput extends keyof TSourceEnumObj>(value: TInput) =>
        value as MapperResult<TSourceEnumObj, TDestEnumObj, TInput>;
    


    언젠가 이 정보가 도움이 되기를 바랍니다. 그렇게 했다면 댓글로 알려주고 개선된 버전을 만들었다면 저도 알고 싶습니다.

    좋은 웹페이지 즐겨찾기