일반 차별적 합집합 축소
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
Type
all 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.)