Typescript 与参数类型匹配的函数返回类型(使用扩展运算符时)?

Typescript 与参数类型匹配的函数返回类型(使用扩展运算符时)?,typescript,typescript-typings,typescript-generics,Typescript,Typescript Typings,Typescript Generics,我是否可以为下面的Mixin()函数定义一个返回类型,该函数将解析为任何参数类型的交集类型 函数混合(…classRefs:any[]){ 返回合并(类{},…classRefs); } 函数合并(派生:any,…classRefs:any[]){ classRefs.forEach(classRef=>{ Object.getOwnPropertyNames(classRef.prototype).forEach(name=>{ 如果(名称!=“构造函数”){ Object.definePro

我是否可以为下面的
Mixin()
函数定义一个返回类型,该函数将解析为任何参数类型的交集类型

函数混合(…classRefs:any[]){
返回合并(类{},…classRefs);
}
函数合并(派生:any,…classRefs:any[]){
classRefs.forEach(classRef=>{
Object.getOwnPropertyNames(classRef.prototype).forEach(name=>{
如果(名称!=“构造函数”){
Object.defineProperty(
原型,
名称
Object.getOwnPropertyDescriptor(classRef.prototype,name)作为PropertyDescriptor
);
}
});
});
衍生收益;
}
福班{
foo(){}
}
分类栏{
bar(){}
}
Baz类{
baz(){
console.log('baz');
}
}
类MyClass扩展了Mixin(Foo,Bar,Baz){}
const my=new MyClass();
my.baz();
如果没有spread操作符,那么泛型就相当简单了。如何确保类型系统与实际发生的情况相匹配

编辑:我修改了示例以匹配我实际使用的mixin函数

编辑:两个后续问题:

  • 如何确保
    …classRefs
    中的所有类扩展一些公共基类?假设我创建了
    抽象类Mergable{}
    ,然后只有子类可以传递给
    merge
  • 静态属性/函数呢?我添加了第二个循环来获取这些,但经过一些实验后,我不确定如何让类型系统意识到这一点:
  • 函数Mixin(…classRefs:[…R]):
    新建(…args:any[])=>UnionToIntersection{
    返回合并(类{},…classRefs);
    }
    函数合并(派生:ClassType,…classRefs:ClassType[]){
    classRefs.forEach(classRef=>{
    Object.getOwnPropertyNames(classRef.forEach)(名称=>{
    常量描述符=Object.getOwnPropertyDescriptor(classRef,name);
    如果(名称!='name'&&name!=='prototype'&&name!=='length'&&descriptor〕){
    Object.defineProperty(
    衍生的,
    名称
    描述符
    )
    }
    });
    //静态特性
    Object.getOwnPropertyNames(classRef.forEach)(名称=>{
    //你可以用这种方法摆脱类型转换
    常量描述符=Object.getOwnPropertyDescriptor(classRef.prototype,名称)
    如果(名称!='name'&&name!=='prototype'&&name!=='length'&&descriptor〕){
    Object.defineProperty(
    原型,
    名称
    描述符
    );
    }
    });
    //实例属性
    Object.getOwnPropertyNames(classRef.prototype).forEach(name=>{
    //你可以用这种方法摆脱类型转换
    常量描述符=Object.getOwnPropertyDescriptor(classRef.prototype,名称)
    if(名称!='constructor'&&descriptor){
    Object.defineProperty(
    原型,
    名称
    描述符
    );
    }
    });
    });
    衍生收益;
    }
    
    您可以编写一个类型函数,该函数接受一个元组,并生成其所有元素的类型。显而易见的方法是:

    本质上,此函数位于第一个构造函数的实例类型
    T
    中;和
    U
    ,来自其余构造函数的实例类型的元组。因此,
    derived
    有一个
    new()=>T
    ,而
    classRefs
    有一个构造签名元组,表示为,从中获取
    U
    的元素并用
    new()=>…
    将其包装起来。返回类型是
    new()=>(T&IntersectAll)
    ,一个生成所有其他构造函数的交集的构造函数

    Mixin()
    也同样通用:

    function Mixin<T extends any[]>(
      ...classRefs: { [I in keyof T]: new () => T[I] }
    ) {
      return merge<unknown, T>(class { }, ...classRefs);
    }
    /* function Mixin<T extends any[]>(
      ...classRefs: { [I in keyof T]: new () => T[I]; }
    ): new () => IntersectAll<T> */
    

    好了。我假设这里有一些边缘案例,因为手动处理原型可以做一些有趣的事情。如果将具有冲突类型的类与相同属性或方法名的类混合在一起,则交集并不完全正确。当然,还有类构造函数参数。这样的边缘情况可能是可寻址的,具有额外的复杂性。。。但我认为这些都超出了范围。


    请让我知道它是否适合您:

    //积分归@jcalz
    类型UnionToIntersection=
    (U扩展任何?(k:U)=>void:never)扩展(
    k:我推断
    )=>无效
    ? 我
    :从不;
    类型推断=
    T扩展推断R
    ? R扩展了new(…args:any)=>any
    ? 实例类型
    :从不
    :从不;
    type ClassType=new(…args:any[])=>any;
    函数Mixin(…classRefs:[…R]):
    新建(…args:any[])=>UnionToIntersection{
    返回合并(类{},…classRefs);
    }
    函数合并(派生:ClassType,…classRefs:ClassType[]){
    classRefs.forEach(classRef=>{
    Object.getOwnPropertyNames(classRef.prototype).forEach(name=>{
    //你可以用这种方法摆脱类型转换
    常量描述符=Object.getOwnPropertyDescriptor(classRef.prototype,名称)
    if(名称!='constructor'&&descriptor){
    Object.defineProperty(
    原型,
    名称
    描述符
    );
    }
    });
    });
    衍生收益;
    }
    福班{
    foo(){}
    }
    分类栏{
    bar(){}
    }
    Baz类{
    baz(){
    console.log('baz');
    }
    }
    类MyClass扩展了Mixin(Foo,Bar,Baz){}
    const my=new MyClass();
    my.foo()//好的
    my.bar()//好的
    my.baz();//好啊
    

    推断类型

    类型推断=/*1*/T扩展推断R/*2*/R扩展新(…参数:任意)=>任意/*3*/实例类型:从不:从不;
    
  • 只是
    // obvious version:
    type IntersectAll<T extends readonly any[]> = 
      T extends [infer F, ...infer R] ? F & IntersectAll<R> : unknown;
    
    // less obvious version:
    type UnionToIntersection<U> =
      (U extends any ? (k: U) => void : never) extends
      ((k: infer I) => void) ? I : never
    type IntersectAll<T extends readonly any[]> = Extract<
      UnionToIntersection<
        { [K in keyof T]: [T[K]] }[number]
      >, readonly any[]>[number]
    
    function merge<T, U extends any[]>(
      derived: new () => T,
      ...classRefs: { [I in keyof U]: new () => U[I] }
    ) {
      classRefs.forEach(classRef => {
        Object.getOwnPropertyNames(classRef.prototype).forEach(name => {
          if (name !== 'constructor') {
            Object.defineProperty(
              derived.prototype,
              name,
              Object.getOwnPropertyDescriptor(
                classRef.prototype, name) as PropertyDescriptor
            );
          }
        });
      });
    
      return derived as new () => (T & IntersectAll<U>);
    }
    
    function Mixin<T extends any[]>(
      ...classRefs: { [I in keyof T]: new () => T[I] }
    ) {
      return merge<unknown, T>(class { }, ...classRefs);
    }
    /* function Mixin<T extends any[]>(
      ...classRefs: { [I in keyof T]: new () => T[I]; }
    ): new () => IntersectAll<T> */
    
    You can verify that it works as expected:
    
    class Foo { foo() { return 6; } }
    class Bar { bar() { return "abc" } }
    class Baz { baz() { console.log('baz'); } }
    class MyClass extends Mixin(Foo, Bar, Baz) { }
    
    const my = new MyClass();
    console.log(my.foo().toFixed(2)); // "6.00"
    console.log(my.bar().toUpperCase()); // "ABC"
    my.baz(); // "baz"