유형 유틸리티 작성 - TypeScript의 공용체 유형에서 인터페이스 파생
실제 사용 사례로 작업하기 위해 비제네릭 코드를 제네릭 유형 유틸리티로 변환하여 더 많은 입력 유형과 함께 작동할 수 있으므로 더 재사용할 수 있습니다.
type Dispatcher = {
[Message in Action as Message['type']]: Message extends { payload: any }
? (payload: Message['payload']) => void
: () => void;
};
제네릭이 아닌 코드, 재사용하기 어려움 ⤴
이 목표를 달성하는 방법에는 여러 가지가 있습니다. 멋진 개발 경험을 제공하기 위해 템플릿 리터럴과 유형 추론을 활용하여 멋진 것을 선택할 것입니다.
type Dispatcher =
ValuesAsCallbacks<UnionToKeyValue<Action, 'type:payload'>>;
재사용 가능한 일반 유형 유틸리티 ⤴
이제 빌드하는 방법을 살펴보겠습니다 🤔
2단계 프로세스
먼저 한 유형을 다른 유형으로 전환하는 프로세스를 분석해 보겠습니다. 다음 섹션에서 구현 및 자세한 설명을 살펴보겠습니다.
1️⃣ 키-값으로의 합집합
먼저 콜론으로 구분된 문자열 리터럴을 제공하고 키로 사용할 속성과 값으로 사용할 속성을 정의하여 유니온을 키-값 인터페이스로 전환합니다. 공용체의 항목에서 속성이 누락된 경우 이 사실을
never
유형으로 나타낼 것입니다.type Action =
| { type: 'reset' }
| { type: 'setValue'; payload: string }
| { type: 'setSelection'; payload: [number, number] | null }
| { type: 'trimWhitespace'; payload: 'leading'|'trailing'|'both' };
type IntermediateResult = UnionToKeyValue<Action, 'type:payload'>;
▲
┌───────────────────────────────────────────────────────────────────┐
│ type IntermediateResult = { │
│ reset: never; │
│ setValue: string; │
│ setSelection: [number, number] | null; │
│ trimWhitespace: 'leading'|'trailing'|'both'; │
│ } │
└───────────────────────────────────────────────────────────────────┘
이것이 콜백이 있는 인터페이스로 어떻게 바뀔지 이미 알 수 있습니까?
2️⃣ 콜백으로서의 값
둘째, 키-값 인터페이스를 키-콜백 인터페이스로 변환하고 있습니다. 여기서 후자의 콜백 함수는 전자의 값과 동일한 유형의 매개변수를 허용합니다.
type IntermediateResult = {
reset: never;
setValue: string;
setSelection: [number, number] | null;
trimWhitespace: 'leading'|'trailing'|'both';
}
type Dispatcher = ValuesAsCallbacks<IntermediateResult>;
▲
┌───────────────────────────────────────────────────────────────────┐
│ type Dispatcher = { │
│ reset: () => void; │
│ setValue: (payload: string) => void; │
│ setSelection: (payload: [number, number] | null) => void; │
│ trimWhitespace: (payload: 'leading'|'trailing'|'both') => void; │
│ } │
└───────────────────────────────────────────────────────────────────┘
구현 및 약간의 설명
키-값에 대한 결합
첫 번째 유틸리티 유형을 구현하는 방법을 살펴보겠습니다 🕵️
type UnionToKeyValue<
T extends { [key: string]: any }, ①
K extends string ②
> = ...
(1) The util accepts an interface, or a union of interfaces, indexed by keys being strings or string literals as a first parameter.
(2) It also requires a second parameter to be a string, or a string literal.
... = K extends `${infer Key}:${infer Value}` ③
? Key extends keyof T ④
? ...
: never
: never;
(3) Key and Value never appeared as a type parameter of the util. Thanks to template literal types, we can infer both from the type provided as the second parameter, so long as it's a string or a string literal, and it contains a colon somewhere inside.
Given
'type:payload'
provided as a second parameter, we're going to infer that Key is'type'
and Value is'payload'
.If we don't find a colon in the second parameter, we fail to pattern-match and we return
never
.(4) Next, with the inferred Key, we double-check it belongs to the key set of the first parameter, T — an interface, or a union of interfaces. If it does not belong there, we return
never
.
? { [A in T as A[Key]]: Value extends keyof A ? A[Value] : never } ⑤
...
(5) Now, we use mapped types to iterate through T, which implies T must be a union of types rather than a single interface to make mapping possible. With an interface, we'd see the use of
keyof
there.The use of
as
combined with indexed accessA[Key]
also gives a clue about elements of the union being interfaces and not primitive types such as undefined or null.Finally, if
A[Key]
does not exist in the element, we're not going to include it in the output. We're not that strict with the Value, ifA[Value]
does not exist, we represent that asnever
.
최종 코드
type UnionToKeyValue<
T extends { [key: string]: any },
K extends string
> = K extends `${infer Key}:${infer Value}`
? Key extends keyof T
? { [A in T as A[Key]]: Value extends keyof A ? A[Value] : never }
: never
: never;
그것은 대부분의 무거운 작업이 완료된 것입니다!
콜백으로서의 값
인터페이스에서 인터페이스로 이동하는 것이 약간 더 쉬울 것입니다. 살펴보겠습니다.
type ValuesAsCallbacks<T extends { [key: string]: any }> = ... ①
(1) This util accepts only one parameter, that must represent an interface with keys being strings or string literals and no constraints on the values.
... = {
[K in keyof T]: T[K] extends never ②
? () => void ③
: (payload: T[K]) => void; ④
};
(2) T is an interface, we iterate through all keys of the provided type, represented as K. Now, we check if the value, T[K], has type
never
.(3) If we detect the value is of the type
never
, we return a type representing a callback not accepting any parameters.(4) Otherwise, we return a type representing a single-parameter callback, where the parameter has the same type as the value in the original interface.
최종 코드
type ValuesAsCallbacks<T extends { [key: string]: any }> = {
[K in keyof T]: T[K] extends never
? () => void
: (payload: T[K]) => void;
};
그것이 바로 인터페이스 조합을 콜백이 있는 인터페이스로 바꾸는 퍼즐의 마지막 조각입니다 🎉 우리가 해냈습니다!
완전한 코드
이 게시물의 전체 코드 목록은 다음에서 찾을 수 있습니다under this link.
TypeScript 놀이터에서 이 게시물의 재사용 가능한 유형 ⤴
최종 단어
이제 하나의 접근 방식을 탐색했으므로 다른 접근 방식(예:
UnionAsKeyValue<T, K>
을 두 개의 개별 유틸리티로 분할)을 시도하고 어떻게 찾는지 확인하는 것이 좋습니다.행운을 빕니다!
Reference
이 문제에 관하여(유형 유틸리티 작성 - TypeScript의 공용체 유형에서 인터페이스 파생), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/maciejsmolinski/building-type-utils-to-derive-interfaces-from-unions-of-interfaces-in-typescript-58k3텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)