在 Typescript Mixin 中传递泛型参数

Pass generic parameter in Typescript Mixin

当使用基于泛型 class 构建的 mixin 时,我需要将其值设置为未知,导致实现 class 具有 unknown | concrete 类型的泛型参数。

我在这里使用 Angular 构建了一个示例,但它完全与 Typescript 相关:https://stackblitz.com/edit/angular-svw6ke?file=src%2Fapp%2Fapp.component.ts

是否有机会重新设计此 mixin(使用 Typescript 4.4)以使类型不会畸形?

更新:更简单的解决方案

您可以放弃混合函数中的 extends 子句,只使用通用 T 来表示您的 BehaviorSubject 类型:

export abstract class ComponentBase<T> {
  model$ = new BehaviorSubject<T>(null);
}

export function genericMixin<T>(Base: AbstractConstructor<ComponentBase<T>>) {
  abstract class GenericMixin extends Base {
    //Logic here
  }
  return GenericMixin;
}

这无需进行任何转换即可工作。您的 AppComponent class 也会有所不同:

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})
export class AppComponent extends genericMixin<Model>(ComponentBase) {
  name = 'Angular';

  method(): void {
    this.model$.subscribe((v) => console.log(v.value));
  }
}

但是您的 model$ 属性 应该仍能正确推断。让我知道这是否有效!

第一个解决方案

您可以提取 AbstractConstructor 基础 class 参数的类型,并从混合函数中显式注释您的 return 类型,如下所示:

// An interface that represents what you're mixing in
interface WithModel<T> {
  model$: BehaviorSubject<T>;
}

// Extracting the type of your `AbstractConstructor` parameter
type ExtractModelType<T extends AbstractConstructor<ComponentBase<any>>> =
  T extends AbstractConstructor<ComponentBase<infer U>> ? U : never;

// Then in your mixin function
export function genericMixin<
  T extends AbstractConstructor<ComponentBase<unknown>>
>(Base: T): T & WithModel<ExtractModelType<T>> {
  abstract class GenericMixin extends Base {
    // Logic here
  }
  return GenericMixin as T & WithModel<ExtractModelType<T>>;
}

现在从派生的 classes 访问 model$ 参数应该会产生正确的类型。

class Model {
  value: string;
}

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})
export class AppComponent extends genericMixin(ComponentBase)<Model> {
  name = 'Angular';

  method(): void {
    // `model$` is correctly inferred as a `Model` here.
    this.model$.subscribe((v) => console.log(v.value));
  }
}

需要通过mixin函数携带泛型参数。 很好地解释了这是如何工作的。对于您的问题,它看起来像

export function genericMixin1<
  ModelType,
  ComponentType extends AbstractConstructor<ComponentBase<ModelType>>
>(Base: T) {
  abstract class GenericMixin extends Base {
    //Logic here
  }
  return GenericMixin;
}

了解他所谓的“两次调用方法”源于 TypeScript 泛型常见问题的变通方法——您不能在调用函数时部分推断泛型类型参数,但您可以在您正在调用的参数和第二个内部函数之间“划分”通用参数。例如:

export function genericMixin2<T>() {
  return function<
    ComponentType extends AbstractConstructor<ComponentBase<T>>
  >(Base: ComponentType) {
    abstract class GenericMixin extends Base {
      //Logic here
    }
    return GenericMixin;
  };
}

(预计到达时间:固定,Playground example here。)

现在您可以写 const StringMixin = (genericMixin2<string>()(ComponentBase)) 而不是写 const StringMixin = genericMixin1<string, ComponentBase<string>>(ComponentBase);。这只是一个通用参数的微小改进,但当您有许多通用参数时,这就变得绝对必要了。否则,你会得到 const ManyParamsMixin = genericMixin<string, number, boolean, BaseThing<string,number,boolean>>(BaseThing)...它很快就会变丑。