在 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)
...它很快就会变丑。
当使用基于泛型 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)
...它很快就会变丑。