有没有办法将联合类型转换为交集类型:
type FunctionUnion = () => void | (p: string) => void
type FunctionIntersection = () => void & (p: string) => void
Run Code Online (Sandbox Code Playgroud)
我想应用转换FunctionUnion
来获取FunctionIntersection
jca*_*alz 113
你想要工会交汇吗? 分配条件类型和条件类型的推断可以做到这一点.(不要认为有可能做交叉联合,抱歉)这是邪恶的魔法:
type UnionToIntersection<U> =
(U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never
Run Code Online (Sandbox Code Playgroud)
分配联盟U
并将其重新包装成一个新的联盟,所有的成员都处于逆境.这允许将类型推断为交集I
,如手册中所述:
同样地,在反变量位置中相同类型变量的多个候选者导致推断交叉类型.
让我们看看它是否有效.
首先让我用括号括起你FunctionUnion
,FunctionIntersection
因为TypeScript似乎比函数返回更紧密地绑定了union/intersection:
type FunctionUnion = (() => void) | ((p: string) => void);
type FunctionIntersection = (() => void) & ((p: string) => void);
Run Code Online (Sandbox Code Playgroud)
测试:
type SynthesizedFunctionIntersection = UnionToIntersection<FunctionUnion>
// inspects as
// type SynthesizedFunctionIntersection = (() => void) & ((p: string) => void)
Run Code Online (Sandbox Code Playgroud)
看起来不错!
请注意,通常会UnionToIntersection<>
公开TypeScript认为是实际联合的一些细节.例如,boolean
显然在内部表示为true | false
,所以
type Weird = UnionToIntersection<string | number | boolean>
Run Code Online (Sandbox Code Playgroud)
变
type Weird = string & number & true & false
Run Code Online (Sandbox Code Playgroud)
希望有所帮助.祝好运!
当您想要几种类型的交集,但不一定将联合转换为交集时,还有一个非常相关的问题。如果不求助于临时工会,就没有办法直接进入十字路口!
问题是我们想要得到交集的类型可能在 内部有联合,这些联合也会被转换为交集。守卫救援:
// 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 }
Run Code Online (Sandbox Code Playgroud)
给定示例中的执行是这样的
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 }
Run Code Online (Sandbox Code Playgroud)
希望这也展示了一些其他有用的技术。
jcalz 对这个问题的解决方案[一如既往]完美,但是,对于那些可能不完全理解它的人,这里对他的回答中的这句话有更多解释:
\n\n\n逆变位置中同一类型变量的多个候选者会导致推断出交集类型。
\n
上面的技巧实际上是类型定义中使用的主要技巧UnionToIntersection
,但是,术语“逆变”对我来说很模糊,谷歌搜索也没有给出有用的结果,所以让我们在这里回顾一下:
根据维基百科:
\n\n\n许多编程语言类型系统都支持子类型。方差是指更复杂类型之间的子类型与其组件之间的子类型之间的关系。例如,猫列表应如何与动物列表相关?或者返回 Cat 的函数应如何与返回 Animal 的函数相关?
\n
我们看一下上面的代码解释:
\ntype A = (a: Animal) => void\ntype C = (d: Cat) => void\ndeclare let aFunc: A;\ndeclare let cFunc: C;\n
Run Code Online (Sandbox Code Playgroud)\n现在,您认为以下哪项作业是正确的?
\ncFunc = aFunc // [ ] \xe2\x9c\x85 [ ]\naFunc = cFunc // [ ] \xe2\x9c\x85 [ ]\n
Run Code Online (Sandbox Code Playgroud)\n首先进行猜测,然后继续阅读。:)
\n与我们对“继承”的期望相反,在“继承”中,子类型的值可以分配给其超类型的变量,但当该变量是函数类型的参数时,这在同一方向上是不正确的,并且我们正在分配“函数”(维基百科的原意是“复杂类型”)。而且有趣的是,它的反方向确实是正确的!即,cFunc = aFunc
上面代码片段中的 是正确的,也是aFunc = cFunc
错误的。
现在让我们看看为什么cFunc = aFunc
是正确的。它正确的原因是,当我们将 Y 类型的某个变量分配给 X 类型的某个变量时,只有当新类型(本例中为 Y)不破坏旧类型的任何用法时,它才是“正确的”输入(本例中为 X)。例如:
a = new Animal()\nc = new Cat()\na = c // \xe2\x9c\x85 Not breaking, everywhere an Animal is used, a Cat must be useable too\n // (It is also formally known as the "Liskov Substitution Principle".)\na.eat() // ---> c.eat() \xe2\x9c\x85 No problem, Cats can eat too\n
Run Code Online (Sandbox Code Playgroud)\n现在,在函数类型的情况下使用相同的规则:如果您将foo
函数类型的函数分配给函数类型的Foo
变量,那么无论您使用什么,它都必须保持可用/有效。bar
Bar
bar
declare let foo: (param: Animal): void\ndeclare let bar: (param: Cat): void\na = new Animal()\nc = new Cat()\n\n// valid usages of foo:\nfoo(a) // \xe2\x9c\x85\nfoo(c) // \xe2\x9c\x85\n\n// valid usage of bar:\nbar(c) // \xe2\x9c\x85\n\nfoo = bar // \xe2\x9d\x8c wrong because \nfoo(a) // \xe2\x9d\x8c this one has not remained useable / valid\n // because foo expects a Cat now, but is receiving an Animal, which is not valid\nfoo(c) // \xe2\x9c\x85\n\nbar = foo // \xe2\x9c\x85 correct because all usages of bar remains still useable / valid\nbar(c) // bar expects an Animal now, and has received a Cat, which is still valid\n // \xe2\xad\x90 That's why we say function parameter is a **contra-variant** position for\n // a type, because it reverses the direction of the assignability.\n
Run Code Online (Sandbox Code Playgroud)\n现在我们可以明白为什么cFunc = aFunc
是正确的选择了!
有趣的边缘情况是,您可以输入一个函数参数 as ,never
这允许您将该参数的任何类型分配给它:
type Foo = (a: never) => void\ntype Bar = (a: Function) => void\ntype Baz = (a: boolean) => void\ntype Qux = (a: SuperComplexType) => void\ndeclare let foo: Foo\ndeclare let bar: Bar\ndeclare let baz: Baz\ndeclare let qux: Qux\nfoo = bar // \xe2\x9c\x85\nfoo = baz // \xe2\x9c\x85\nfoo = qux // \xe2\x9c\x85\n
Run Code Online (Sandbox Code Playgroud)\n使用相同的猫和动物示例的所有三个 co/contra/in 方差的摘要是:
\n() => Cat
可分配给() => Animal
,因为 Cat 可分配给 Animal ;它“保留了可分配性的方向”。(Animal) => void
可分配给(Cat) => void
,因为需要动物的东西也可以接受猫;它“逆转了可分配性的方向”。(Animal) => Animal
不可分配给(Cat) => Cat
,因为并非所有返回的 Animal 都是猫,并且(Cat) => Cat
不可分配给 (Animal) => Animal,因为期待 Cat 的东西不能接受任何其他类型的 Animal。现在这就是 jcalz'UnionToIntersection
工作原理:
type FirstHalfOfUnionToIntersection<U> = U extends any ? (k: U)=>void : never\n
Run Code Online (Sandbox Code Playgroud)\nU
这是一个分布式条件条件(因为前面的类型extends
是裸类型(单独出现并且不是某些更复杂类型表达式的一部分)),因此为联合的每个组件运行条件,例如,在 的情况下,X | Y | Z
它产生((k: X) => void) | ((k: Y) => void) | ((k: Z) => void)
.
在该类型的后半部分,它实际上是这样做的:
\n<A_union_of_some_functions_from_first_half> extends ((k: infer I)=>void) ? I : never\n
Run Code Online (Sandbox Code Playgroud)\n这又是一个分布式条件,但是,这是有趣的部分:正在推断的类型I
处于逆变位置(它是一个函数参数),因此它的所有可能的推断都将被相交!
\n\n逆变位置中同一类型变量的多个候选者会导致推断出交集类型。
\n
例如,继续相同的X | Y | Z
示例,结果将是X & Y & Z
。
我稍微扩展了 @jcalz 的答案,以解决他描述的布尔问题。
type UnionToIntersectionHelper<U> = (
U extends unknown ? (k: U) => void : never
) extends (k: infer I) => void
? I
: never;
type UnionToIntersection<U> = boolean extends U
? UnionToIntersectionHelper<Exclude<U, boolean>> & boolean
: UnionToIntersectionHelper<U>;
Run Code Online (Sandbox Code Playgroud)
这基本上可以防止它将 under the hood 转换true | false
为 a true & false
,从而保留boolean
其本质。
现在它会正确地说UnionToIntersection<boolean>
is boolean
, not never
,同时仍然正确地说UnionToIntersection<boolean | string>
isnever
归档时间: |
|
查看次数: |
4645 次 |
最近记录: |