如果你有一个价值v
类型的V
和一个值w
类型的W
,然后是数组[v, w]
是类型Array<V | W>
,其中元素类型是union https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#union-types(因为每个元素要么是V
or a W
);它不是类型Array<V & W>
其中元素类型是路口 https://www.typescriptlang.org/docs/handbook/2/objects.html#intersection-types(这意味着每个元素都是both a V
and a W
,这是不真实的)。所以你不能只从数组中推断出元素类型,然后直接将其用作合并的东西。
相反,您想要做的是解释类型plugins
as a 元组类型 https://www.typescriptlang.org/docs/handbook/2/objects.html#tuple-types因此编译器分别知道每个元素的类型,然后使用一些类型魔法通过交集将元组类型的元素组合成单个类型。这是一种方法:
type TupleToIntersection<T extends any[]> = {
[I in keyof T]: (x: T[I]) => void }[number] extends
(x: infer I) => void ? I : never;
declare function
renderer<T extends PluginType<Record<string, unknown>>[]>(
plugins?: [...T]):
() => TupleToIntersection<T>;
So renderer()
is generic https://www.typescriptlang.org/docs/handbook/2/generics.html in T
,类型plugins
。注意我已经标记了plugins
作为有类型[...T]
代替T
。那是可变元组类型 https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-0.html#variadic-tuple-types语法,它只是给编译器一个它应该解释的提示plugins
作为元组而不是无序数组。
一旦我们有T
,我们计算TupleToIntersection<T>
是元素类型的交集T
。 (因此,如果T
is [V, W, X, Y]
, then TupleToIntersection<T>
is V & W & X & Y
.) 这是通过使用来完成的分配条件类型 https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types and 条件类型推断 https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#inferring-within-conditional-types。这类似于如何the UnionToIntersection<T>类型作品 https://stackoverflow.com/q/50374908/2887218:我获取元组的每个元素并将其映射到函数参数类型([V, W, X, Y]
变成[(x: V)=>void, (x: W)=>void, (x: X)=>void, (y: Y=>void)]
),这是一个逆变 https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)#Contravariant_method_parameter_type位置。然后我将其合并到函数的联合中(((x: V) => void)|((x: W) => void)|((x: X) => void)|((x: Y) => void)
),并从那里推断出与其匹配的单个函数参数类型。函数并集通过逆变接受参数的交集,因此编译器可以推断(x: infer I) => void
(where I
在这个例子中会变成V & W & X & Y
)。如果这太令人困惑了……呃,这很神奇????♂️✨????♀️。
无论如何,让我们测试一下:
const plugins1: PluginType<{ boo?: string }> = { key: '', boo: '' };
const plugins2: PluginType<{ foo?: string }> = { key: '', foo: '' };
const render = renderer([plugins1, plugins2]);
/* const render: () => {
boo?: string | undefined;
} & {
key: string
} & {
foo?: string | undefined;
} */
render().boo // string | undefined
render().foo // string | undefined
看起来不错!返回值是一个返回两个元素类型的交集的函数,因此render()
已知有一个boo
and a foo
财产,根据需要。
Playground 代码链接 https://www.typescriptlang.org/play?#code/C4TwDgpgBACgNgVwOYEsB2AVcEA8GoQAewEaAJgM5QBKEAxgPYBOZOFwT6SANFAmgGs0DAO5oAfOKgBeKPgBkUAN5QBEEAC4o7TmiQBuKAF8AUCdCQ5CMHAgYGASTQkmFesBQM0eAsVKUoAEM0EABtAF0pWSUTKChQhyh0VXUGADM5cK0ACkItDATwgEoZKQA3BhQyY1C0BABbACMIJnDfEnIKWKhcrXQ0lqgHEulyyuqAfiGoLTQIMpb9MzJ6OECmaDT+Og8vbo3yFpafIg6A+GR0LEgcWkYWNg4uXn4hUQlxCPFs7ribS7QFAmWlCADpwRhihputkRlIMNZbPYnC43DtPN4MOIliZGIDgFB-qhAQBGLQXYnXXAqRoMBjA7RPPTGKLKFKaKAAck5vFpDC03OMSzx7EJiGJFAATOTxVdsDgVGk6QydFwWTI2WoOdzeEr+VzOUKzCKCQcVkwNWajkxsqEiegKCTePbAZLiksAPQAKigJqgVqYOThyl+UD5KqZSCgAB8+Ic0ugIGQlkYoIoYnE4lqtKrmSZU+nQ3qI7oo7H+CsE3Nk-m4l6PWYA7DQXyoB6PYzSzG45XE2QTE2iqC9W2O7myz2IFWk0A