일반 차별적 합집합 축소
19740 단어 typescriptprogramming
This post was originally a note on my Digital Garden 🌱
TypeScript에서 태그가 지정된 조합이라고도 하는 식별된 조합으로 작업할 때 종종 조합의 값을 멤버 중 하나의 유형으로 좁혀야 합니다.
type Creditcard = {tag: 'Creditcard'; last4: string}
type PayPal = {tag: 'Paypal'; email: string}
type PaymentMethod = Creditcard | PayPal
위의 유형이 주어지면 값이 사용자의 이메일을 표시하기 위해 PayPal 유형인지 또는 마지막 4자리를 표시하기 위해 신용카드 유형인지 확인하려고 합니다.
이 작업은 간단하지만 판별 속성(이 경우
tag)을 확인하면 됩니다. PaymentMethod 유형의 두 구성원은 각각 tag 속성에 대해 서로 다른 유형을 가지므로 TypeScript는 유형을 좁히기 위해 이들을 구별할 수 있습니다. 따라서 태그가 지정된 조합 이름입니다.declare const getPaymentMethod: () => PaymentMethod
const x = getPaymentMethod()
if (x.tag === 'Paypal') {
console.log("PayPal email:", x.email)
}
if (x.tag === 'Creditcard') {
console.log("Creditcard last 4 digits:", x.last4)
}
tag 속성에 대한 이러한 명시적 검사를 피하기 위해 type predicates 또는 가드를 정의할 수 있습니다.const isPaypal = (x: PaymentMethod): x is PayPal =>
x.tag === 'Paypal'
const x = getPaymentMethod()
if (isPayPal(x)) {
console.log("PayPal email:", x.email)
}
이러한 가드의 문제는 애플리케이션에서 식별된 각 공용체 유형의 각 멤버에 대해 정의해야 한다는 것입니다. 아니면 그들은?
나와 같다면 모든 공용체 유형에 대해 일반적으로 이 작업을 수행하는 방법이 궁금할 것입니다. 일부 TypeScript 유형 수준의 흑 마법으로도 가능할 수 있습니까?
짧은 대답은 '예'입니다.
const isMemberOfType = <
Tag extends string,
Union extends {tag: Tag},
T extends Union['tag'],
U extends Union & {tag: T}
>(
tag: T,
a: Union
): a is U => a.tag === tag
적절한 응답은 도대체 해킹이 뭐야?
알겠습니다. 설명하겠습니다.
네 가지 일반 유형이 필요합니다. 실제로 호출자는 이를 전달할 필요가 없으며 TypeScript가 유형 추론 비즈니스를 수행할 수 있도록 하는 목적이 있습니다.
그들 모두에 대해 어떤 유형도 허용하지 않고 대신
extends를 사용하여 해당 일반 유형이 특정 조건을 충족해야 함을 TypeScript에 알립니다.Tag는 Union의 모든 태그의 합집합입니다. 태그의 기본 유형을 확장해야 합니다. 이 경우에는 string Union는 태그가 지정된 공용체 유형이며 tag 유형의 속성을 가진 객체여야 합니다.Tag는 범위를 좁히려는 특정 태그이므로 T 유형Tag는 범위를 좁히려는 U의 특정 구성원이며, Union를 확장해야 하며 해당 태그는 Union, 원하는 특정 구성원T 및 Tag 는 좁히려는 공용체 유형을 정의하기 위해 존재하며 Union 및 T 는 더 나은 이름을 찾는 게으름이 없기 때문에 좁혀진 유형이므로 마법이 발생합니다. 우리는 얻을 것으로 기대합니다.const x = getPaymentMethod()
if (isMemberOfType('Creditcard', x)) {
console.log('Creditcard last 4 digits:', x.last4)
}
if (isMemberOfType('Paypal', x)) {
console.log('PayPal email:', x.email)
}
Et voilà, 작동합니다!
이것이 TypeScript가 제네릭의 구멍을 채우는 방법입니다.
U의 변형이 있는데 유형 조건자 대신 일종의 getter로 작동하는 것을 사용하고 싶습니다.const getMemberOfType = <
Tag extends string,
Union extends {tag: Tag},
T extends Union['tag'],
U extends Union & {tag: T}
>(
tag: T,
a: Union
): U | undefined =>
isMemberOfType<Tag, Union, T, U>(tag, a) ? a : undefined
사용법은 비슷하지만 물론 null 검사가 필요합니다.
const cc = getMemberOfType('Creditcard', getPaymentMethod())
if (cc) {
console.log('Creditcard last 4 digits:', cc.last4)
}
const pp = getMemberOfType('Paypal', getPaymentMethod())
if (pp) {
console.log('PayPal email:', pp.email)
}
약간의 문제가 있습니다. 그것이 반환하는 유추 유형은 제네릭(
isMemberOfType )에 대한 우리의 정의를 기반으로 하기 때문에 그리 좋지 않습니다.실제로 이것은 문제가 되지 않지만 여기까지 왔으니 계속 진행할 수 있습니다.
TypeScript의 유형 유틸리티 중 하나인 Extract 을 입력합니다.
Constructs a type by extracting from
Typeall union members that are assignable toUnion.
예를 들어 다음과 같은 경우
U extends Union & {tag: T}는 T0 유형이 됩니다.type T0 = Extract<'a' | 'b' | 'c', 'a' | 'f'>
정의는 간단합니다.
// Extract from Type those types that are assignable to Union
type Extract<Type, Union> = Type extends Union ? Type : never
'a' 및 isMemberOfType 의 현재 정의에서 반환된 유형은 union: getMemberOfType 를 확장합니다.PayPal의 경우
U extends Union & {tag: T}가 됩니다. 반환된 유형에 PayPal & { tag: 'PayPal' }를 추가하면 대신 Extract를 얻을 수 있습니다.const isMemberOfType = <
Tag extends string,
Union extends {tag: Tag},
T extends Union['tag'],
U extends Union & {tag: T}
>(
tag: T,
a: Union
): a is Extract<Union, U> => a.tag === tag
const getMemberOfType = <
Tag extends string,
Union extends {tag: Tag},
T extends Union['tag'],
U extends Union & {tag: T}
>(
tag: T,
a: Union
): Extract<Union, U> | undefined =>
isMemberOfType<Tag, Union, T, U>(tag, a) ? a : undefined
이 방법이 훨씬 깔끔합니다! 이제 안심하고 잠들 수 있습니다...
결론적으로, 우리는 단순한 판별 속성 검사에서 동일한 결과를 달성하는 엄청난 제네릭 및 유형 추론으로 이동했습니다. 프로덕션 환경에서 이러한 유틸리티를 사용해야 합니까? 물론! 그것이 우리 동료들을 혼란스럽게 한다면 더욱 그렇습니다. 아닐 수도 있지만 TypeScript로 달성할 수 있는 멋진 것들을 발견하는 것은 즐거웠습니다.
Here's the final version of the examples in the TypeScript Playground .
Reference
이 문제에 관하여(일반 차별적 합집합 축소), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/gillchristian/generic-discriminated-union-narrowing-2181텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)