Birleşim türünü kesişim türüne dönüştür


92

Bir birleşim türünü kesişim türüne dönüştürmenin bir yolu var mı:

type FunctionUnion = () => void | (p: string) => void
type FunctionIntersection = () => void & (p: string) => void

Ben bir dönüşüm uygulamak istiyorum FunctionUnionalmakFunctionIntersection

Yanıtlar:


200

Kavşak için birleşmek mi istiyorsunuz? Dağıtık koşullu türler ve koşullu türlerden çıkarım bunu yapabilir. (Yine de birleşmek için kesişim yapmanın mümkün olduğunu düşünmeyin, üzgünüm) İşte kötü sihir:

type UnionToIntersection<U> = 
  (U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never

Bu, sendikayı dağıtır ve Utüm konseylerin çelişkili bir konumda olduğu yeni bir birliğe yeniden paketler. Bu I, el kitabında belirtildiği gibi türün bir kesişim olarak çıkarılmasına izin verir :

Benzer şekilde, karşıt varyant konumlarında aynı tür değişken için birden fazla aday, bir kesişim türünün çıkarılmasına neden olur.


İşe yarayıp yaramadığını görelim.

Öncelikle parantezlememe izin verin FunctionUnionve FunctionIntersectionTypeScript birleşimi / kesişimi function return'ten daha sıkı bir şekilde bağladığı için:

type FunctionUnion = (() => void) | ((p: string) => void);
type FunctionIntersection = (() => void) & ((p: string) => void);

Test yapmak:

type SynthesizedFunctionIntersection = UnionToIntersection<FunctionUnion>
// inspects as 
// type SynthesizedFunctionIntersection = (() => void) & ((p: string) => void)

İyi görünüyor!

Genel olarak UnionToIntersection<>, TypeScript'in gerçek bir birliktelik olduğunu düşündüğü bazı ayrıntıları ortaya çıkardığına dikkat edin . Örneğin boolean, görünüşe göre dahili olarak temsil edilir true | false, yani

type Weird = UnionToIntersection<string | number | boolean>

olur

type Weird = string & number & true & false

TS3.6 + 'da hevesle indirgenen

type Weird = never

çünkü string ve number ve true ve olan bir değere sahip olmak imkansızdır false.

Umarım yardımcı olur. İyi şanslar!


14
10x. Senden her zaman yeni ilginç şeyler öğreniyorum. Bu soruya çok
yakındım

2
Bu cevap müthiş ama gerçekten bu bölüm "Hepsi bu consitutents olan yeni birlik içine birliği U ve repackages dağıtır nasıl anlamak zor bulmak kontravaryant pozisyon eser" :( Tam olarak bu idrak edemez kontravaryant pozisyon parçasını Bu kodun type Param<T> = T extends (arg: infer U) => void ? U : never; type InferredParams = Param<((a: string) => void) | ((a: number) => void)>;bana vermesi gerektiğini düşündüm string & numberama bana veriyor string | numberNedenini açıklayabilir misin?
Mariusz Pawelski

10
Bunun nedeni extends, koşullu türdeki çıplak tür parametrelerinin herhangi bir birleşim bileşenine dağıtılmış olmasıdır . Eğer devre dışı dağıtılan koşullu türlerine isterseniz, böyle böyle bir tek eleman başlığın olarak türü parametresi "giyinik", yapma hile kullanabilirsiniz: type Param<T> = [T] extends [(arg: infer U) => void] ? U : never;. Bu senin istediğin şekilde çalışmalı.
jcalz

3
@RanLottem anahtar dağıtım koşullu türlerdir . El kitabı bence bunu oldukça iyi açıklıyor. Daha fazla bilgiye ihtiyaç duyduğunuz başka bir yere genişlettim . İyi şanslar!
jcalz

1
@NicholasCuthbert dikkatli olun; unknown"doğru" sonuçtur. Hepsi için Ave B, UTI<A | B>eşit olmalıdır UTI<A> & UTI<B>, doğru? Izin Bolmak neverve not A | neverolduğunu A. Şimdi UTI<A> = UTI<A> & UTI<never>her şeye sahipsin A. Bunu tatmin etmenin tek makul yolu UTI<never>= unknownçünkü X & unknowneşittir X. Eğer UTI<never>vardı never, o zaman UTI<A> = UTI<A> & never, ancak o zamandan beri X & never= neverdiyorsunuz UTI<A> = neverherkes için Abüyük değil .... Olmak için kullanım durumları UTI<never>olabilir never, ama bana görünmüyor, bu yüzden dikkatli olun.
jcalz

7

Ayrıca, birkaç türden bir kesişme istediğinizde, ancak sendikaları kavşaklara dönüştürmeniz gerekmediğinde de çok ilgili bir sorun vardır. Geçici sendikalara başvurmadan kavşaklara girmenin hiçbir yolu yok!

Sorun şu ki, kesişimini elde etmek istediğimiz türlerin içinde sendikalar olabilir ve bunlar da kesişimlere dönüştürülür. Kurtarma muhafızları:

// union to intersection converter by @jcalz
// Intersect<{ a: 1 } | { b: 2 }> = { a: 1 } & { b: 2 }
type Intersect<T> = (T extends any ? ((x: T) => 0) : never) extends ((x: infer R) => 0) ? R : never

// get keys of tuple
// TupleKeys<[string, string, string]> = 0 | 1 | 2
type TupleKeys<T extends any[]> = Exclude<keyof T, keyof []>

// apply { foo: ... } to every type in tuple
// Foo<[1, 2]> = { 0: { foo: 1 }, 1: { foo: 2 } }
type Foo<T extends any[]> = {
    [K in TupleKeys<T>]: {foo: T[K]}
}

// get union of field types of an object (another answer by @jcalz again, I guess)
// Values<{ a: string, b: number }> = string | number
type Values<T> = T[keyof T]

// TS won't believe the result will always have a field "foo"
// so we have to check for it with a conditional first
type Unfoo<T> = T extends { foo: any } ? T["foo"] : never

// combine three helpers to get an intersection of all the item types
type IntersectItems<T extends any[]> = Unfoo<Intersect<Values<Foo<T>>>>

type Test = [
    { a: 1 } | { b: 2 },
    { c: 3 },
]

// this is what we wanted
type X = IntersectItems<Test> // { a: 1, c: 3 } | { b: 2, c: 3 }

// this is not what we wanted
type Y = Intersect<Test[number]> // { a: 1, b: 2, c: 3 }

Verilen örnekteki uygulama şu şekildedir

IntersectItems<[{ a: 1 } | { b: 2 }, { c: 3 }]> =
Unfoo<Intersect<Values<Foo<[{ a: 1 } | { b: 2 }, { c: 3 }]>>>> =
Unfoo<Intersect<Values<{0: { foo: { a: 1 } | { b: 2 } }, 1: { foo: { c: 3 } }}>>> =
Unfoo<Intersect<{ foo: { a: 1 } | { b: 2 } } | { foo: { c: 3 } }>> =
Unfoo<(({ foo: { a: 1 } | { b: 2 } } | { foo: { c: 3 } }) extends any ? ((x: T) => 0) : never) extends ((x: infer R) => 0) ? R : never> =
Unfoo<(({ foo: { a: 1 } | { b: 2 } } extends any ? ((x: T) => 0) : never) | ({ foo: { c: 3 } } extends any ? ((x: T) => 0) : never)) extends ((x: infer R) => 0) ? R : never> =
Unfoo<(((x: { foo: { a: 1 } | { b: 2 } }) => 0) | ((x: { foo: { c: 3 } }) => 0)) extends ((x: infer R) => 0) ? R : never> =
Unfoo<{ foo: { a: 1 } | { b: 2 } } & { foo: { c: 3 } }> =
({ foo: { a: 1 } | { b: 2 } } & { foo: { c: 3 } })["foo"] =
({ a: 1 } | { b: 2 }) & { c: 3 } =
{ a: 1 } & { c: 3 } | { b: 2 } & { c: 3 }

Umarım bu, diğer bazı yararlı teknikleri de gösterir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.