为什么打字稿混合需要一个带有单个剩余参数any[]的构造函数?

kev*_*ler 11 typescript webpack typescript-typings

我正在将一个功能性 mixin 项目从 javascript 迁移到 typescript。我所有的 javascript minxins 都有带有单个参数的构造函数签名constructor(props){}

在打字稿中,我按照https://www.typescriptlang.org/docs/handbook/mixins.html的官方文档定义了 mixin 构造函数类型 :

export type ConstrainedMixin<T = {}> = new (...args: any[]) => T;
Run Code Online (Sandbox Code Playgroud)

即使我将该 mixin 签名更改为:

export type ConstrainedMixin<T = {}> = new (props: any) => T;
Run Code Online (Sandbox Code Playgroud)

并更新实现 TSC 将抛出错误:

TS2545:mixin 类必须具有一个带有“any[]”类型的单个剩余参数的构造函数。

不幸的是,它无法为传递给构造函数的参数创建唯一的类型签名。我现在还需要迁移所有现有的构造函数。如何为 mixin 构造函数创建更明确的类型接口?

我创建了一个游乐场示例

在此输入图像描述

您可以在此屏幕截图中看到编译器在 MIXIN 定义上出现错误,并表示 mixin 类必须具有单个剩余参数。即使类型定义是:

type ConstrainedMixin<T = {}> = new (props: any) => T;
Run Code Online (Sandbox Code Playgroud)

在我的示例中,const mg = new MixedGeneric({cool: 'bro'}); 我想为该{cool: 'bro'}对象创建一个接口,并从 mixin 定义中强制执行它。我不确定如何正确执行此操作。如果构造函数是...args: any[]


更新 听起来这可能是一些反模式,所以这里有进一步的解释。我正在构建一个实体组件系统。在我当前的实现中,我有一些 mixins 链,例如:

const MixedEntity = RenderMixin(PhysicsMixin(GeometryMixin(Entity));
const entityInstance = new MixedEntity({bunch: "of", props: "that mixins use"});
Run Code Online (Sandbox Code Playgroud)

当最终的 MixedEntity 被实例化时,它会被传递一个props数据包对象。所有 Mixin 在其构造函数中都有自己的初始化逻辑,用于查找对象的特定属性props

我以前的 mixin 类的构造函数如下:

constructor(props){
  super(props);
  if(props.Thing) // do props.thing
}
Run Code Online (Sandbox Code Playgroud)

我现在必须将构造函数迁移到:

constructor(...args: any[]){
  const props = args[0]
  super(props);
  if(props.Thing) // do props thing
}   
Run Code Online (Sandbox Code Playgroud)

hac*_*ape 18

TS 中的 Mixin 模式旨在使用额外的方法或属性扩展基类,但不会篡改构造函数签名。因此,派生类应该保持其构造函数签名与其扩展的基类相同。

\n

这就是这个限制背后的原因,A mixin class must have a constructor with a single rest parameter of type \'any[]\'因为 TS 不关心,它只是将构造传递给它super(\xe2\x80\xa6args)并让它完成工作。

\n

因此,如果您想约束构造函数参数,只需在基类构造函数签名中执行即可。

\n
\ntype Constructor = new (...args: any[]) => {};\nfunction MixinGeneric<TBase extends Constructor>(Base: TBase) {\n  return class extends Base {\n    // \xe2\x80\xa6mixin traits\n  }\n}\n\nclass Generic {\n  // ctor param constraint goes here:\n  constructor<P extends { cool: string }>(props: P) {}\n}\n\n// and it\xe2\x80\x99ll check:\nconst mg = new MixedGeneric({cool: \'bro\'});\n
Run Code Online (Sandbox Code Playgroud)\n
\n

响应OP的更新而更新

\n

是的,篡改构造函数签名被视为反模式。但程序员总是违反规则。只要您了解自己在做什么,就一定要有创造力。

\n

去游乐场看看吧。

\n

技巧是通过使用类型断言和一堆实用程序类型来绕过 TS 限制。

\n
// Utility types:\n\ntype GetProps<TBase> = TBase extends new (props: infer P) => any ? P : never\ntype GetInstance<TBase> = TBase extends new (...args: any[]) => infer I ? I : never\ntype MergeCtor<A, B> = new (props: GetProps<A> & GetProps<B>) => GetInstance<A> & GetInstance<B>\n\n\n// Usage:\n// bypass the restriction and manually type the signature\nfunction GeometryMixin<TBase extends MixinBase>(Base: TBase) {\n  // key 1: assert Base as any to mute the TS error\n  const Derived = class Geometry extends (Base as any) {\n    shape: \'rectangle\' | \'triangle\'\n    constructor(props: { shape: \'rectangle\' | \'triangle\' }) {\n      super(props)\n      this.shape = props.shape\n    }\n  }\n\n  // key 2: manually cast type to be MergeCtor\n  return Derived as MergeCtor<typeof Derived, TBase>\n}\n
Run Code Online (Sandbox Code Playgroud)\n