typescript - 如何使合并类函数能够在 Typescript 中接受 n 个类参数?
问题描述
所以我有一个 mergeClass 函数,它接受各种类并将它们合并,这样一个变量将具有所有这些类成员的所有属性,并且它都是实例化的。我目前这样做的方式是我必须合并三个类,但假设我只需要 2、5 或 n 个可以合并的类?我将如何做到这一点?基本上我希望能够通过任意数量的课程。我开始认为这是不可能的,如果是这样,请告诉我。谢谢。
type Constructable = new (...args: any[]) => any;
const mergeClasses = <S extends Constructable, T extends Constructable, P extends Constructable>(class1: S, class2: T, class3: P) =>
<Si extends InstanceType<S> = InstanceType<S>,
Ti extends InstanceType<T> = InstanceType<T>,
Pi extends InstanceType<P> = InstanceType<P>>
(args1: ConstructorParameters<S>, args2: ConstructorParameters<T>, args3: ConstructorParameters<P>): Si & Ti & Pi => {
const obj1 = new class1(...args1);
const obj2 = new class2(...args2);
const obj3 = new class3(...args3);
for (const p in obj2) {
obj1[p] = obj2[p];
}
for(const p in obj3){
obj1[p] = obj3[p];
}
return obj1 as Si & Ti & Pi;
};
//I need something like this maybe in my mergeClasses function?
for(let i = 0; i<classes.length; i++){
let obj = classes[i]
const obj1 = new obj();
for (const p in obj) {
obj1[p] = obj[p];
}
}
const mergeE = mergeClasses(B, C, D);
const e = mergeE<B, C, D>([], [], []);
这是一个 Typescript Playground MergeClasses 函数
另外,我冒着对这个问题表示反对的风险,如果是你,那很好,非常欢迎你对我表示反对,但至少让我知道这是否可能做到,这样我就可以至少继续以对成功的现实期望来解决这个问题。
解决方案
你mergeClasses
是有限的,因为你已经使用泛型来描述每个单独的类。C
我们可以通过使用单个泛型来描述元组类型的构造函数,使其接受任意数量的类。
const mergeClasses = <C extends Constructable[]>(...classes: C) =>
为了获取 args 数组的类型,我们需要将映射类型应用于元组。
为了获取组合实例的类型,我们使用另一个映射类型来获取所有实例的元组。索引[number]
on 为我们提供了所有实例类型的联合。我们可以应用@jcalz 著名的UnionToIntersection
实用程序类型来获取实例类型的交集。
type Constructable = new (...args: any[]) => any;
// mapped type to apply ConstructorParameters to a tuple
type ToArgs<T> = {
[K in keyof T]: T[K] extends new (...args: infer A) => any ? A : never;
}
// mapped type to apply InstanceType to a tuple
type ToInstance<T> = {
[K in keyof T]: T[K] extends new (...args: any[]) => infer I ? I : never;
}
// utility type to convert a union to an instersection
type UnionToIntersection<U> =
(U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never
// the type of the instance that the merged class creates
type CombinedInstance<C extends Constructable[]> = UnionToIntersection<ToInstance<C>[number]>
const mergeClasses = <C extends Constructable[]>(...classes: C) =>
(...argsArrays: ToArgs<C>): CombinedInstance<C> => {
const obj = {} as CombinedInstance<C>
classes.forEach((someClass, i) => {
const newObj = new someClass(argsArrays[i]);
for (const p in newObj) {
// p has type `string` so we need to assert
obj[p as keyof CombinedInstance<C>] = newObj[p];
}
});
return obj;
};
const mergeE = mergeClasses(B, C, D); // type: (argsArrays_0: [], argsArrays_1: [], argsArrays_2: []) => B & C & D
const e = mergeE([], [], []); // type: B & C & D
console.log(e); // logs properties of B, C, and D
这是接受与您之前的格式相同的 args ([], [], [])
。我不喜欢这种格式,因为输入一堆空数组感觉很草率。...args
如果每个构造函数都可以接受不同数量的参数,那么在确保将正确的参数传递给正确的构造函数的同时,很难将其扁平化。
如果你所有的类都没有参数,你绝对可以让这个更干净。
const mergeClasses = <C extends (new () => any)[]>(...classes: C) =>
(): CombinedInstance<C> => {
const obj = {} as CombinedInstance<C>
classes.forEach(someClass => {
const newObj = new someClass();
for (const p in newObj) {
// p has type `string` so we need to assert
obj[p as keyof CombinedInstance<C>] = newObj[p];
}
});
return obj;
};
const mergeE = mergeClasses(B, C, D); // type: () => B & C & D
const e = mergeE(); // type: B & C & D
您还可以要求它们都具有一个作为 props 对象的参数,并让您的合并类接受具有所有属性的对象(假设没有一个具有冲突的属性类型)。
// now takes only 1 argument
type Constructable = new (props: any) => any;
// source: https://stackoverflow.com/a/50375286/10431574
type UnionToIntersection<U> =
(U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never
// mapped type to apply InstanceType to a tuple
type ToInstance<T> = {
[K in keyof T]: T[K] extends new (...args: any[]) => infer I ? I : never;
}
// now extracts only 1 argument
type ToArgs<T> = {
[K in keyof T]: T[K] extends new (props: infer A) => any ? A : never;
}
// the type of the instance that the merged class creates
type CombinedInstance<C extends Constructable[]> = UnionToIntersection<ToInstance<C>[number]>
// need to combine all args to one like with the instances
type CombinedArgs<C extends Constructable[]> = UnionToIntersection<ToArgs<C>[number]>
const mergeClasses = <C extends Constructable[]>(...classes: C) =>
(props: CombinedArgs<C>): CombinedInstance<C> => {
const obj = {} as CombinedInstance<C>
classes.forEach((someClass, i) => {
// pass the whole props object to each constructor
const newObj = new someClass(props);
for (const p in newObj) {
// p has type `string` so we need to assert
obj[p as keyof CombinedInstance<C>] = newObj[p];
}
});
return obj;
};
class B {
b: string;
constructor({ b }: { b: string }) {
this.b = b;
}
}
class C {
c: string;
constructor({ c }: { c: string }) {
this.c = c;
}
}
class D {
d: string;
constructor({ d }: { d: string }) {
this.d = d;
}
}
const mergeE = mergeClasses(B, C, D); // type: (props: { b: string; } & { c: string; } & { d: string; }) => B & C & D
const e = mergeE({ b: "b prop", c: "c prop", d: "d prop" }); // type: B & C & D
console.log(e);
推荐阅读
- time - 为什么两个计时时间点的差异会导致秒数?
- c++ - 调试延迟加载dll
- html - 为每个手动增加 td 大小
- javascript - 如何使 Bluejeans 徽标在 iframe 中显示:none?
- ssl - Apache2 VirtualHost SSL 配置问题
- python - python已安装但zsh无法识别
- hook - 如何在 kedro 0.17.0 中加载特定的目录数据集实例?
- sass - Sass 总是向正确的地图抛出错误
- angular - TypeError: t.toFloat is not a function TensorFlow with Angular
- php - Laravel - how to select items by calculated deadline date