这是我的班级:

class Foo {
  static actions = ["func1", "func2"];
  a: number;
  func1(str: string) {
  };
  func2(n: number) {
  };
  func3() {}
}

我的目标是对一个泛型函数(下面的wrapIt函数)进行适当的类型脚本注释,该泛型函数接受任何类(如Foo,具有一组随机的属性和函数),并返回一个仅实现来自静态字段actions的给定操作的类:

function wrapIt(MyClass) {
  return class {
    constructor() {
      for (const actionName of MyClass.actions) {
        const fn = MyClass.prototype[actionName];
        this[actionName] = (...args: any[]) => {
          console.log("do something here that just call the original function");
          return fn.bind(this)(...args);
        }
      }
    }
  }
}

const NewClass = wrapIt(Foo)

const c = new NewClass();
c.func1("ok"); // typescript error: func1 is not known

Result中的func1func2可能是原始的func1func2,但用一些代码包装

我用纯JavaScript编写这种代码并不困难,但在打印脚本中,这对我来说是地狱:-|

TS playground

推荐答案

Foo中,您需要通过添加as const来使actions成为编译时常量:

class Foo {
    // ...
    static actions = ["func1", "func2"] as const;
    // ...                             ^^^^^^^^^
}

然后,我们可以对wrapIt的泛型类型参数执行此操作.首先,让我们为构造函数定义一个可重用的类型:

type ConstructorFunction = new (...args: any[]) => any;

现在我们需要定义wrapIt参数的类型.它需要:

  • 构造函数
  • 具有actions属性,该属性是有效方法名称的数组

我们需要方法名作为它自己的类型参数.为了限制它们,我们需要扩展keyof由构造函数创建的对象类型,我们可以通过实用程序类型InstanceType获得该类型.这看起来是这样的:

function wrapIt<Cls extends ConstructorFunction, Method extends keyof InstanceType<Cls>>(
    MyClass: Cls & { actions: readonly Method[] }
) {
    // ...
}

(我实际上并没有将actions个名称限制为仅method个名称,仅限于实例属性名称.如果您不想让用户将"a"放在actions中,您可以更进一步,但我在这里没有麻烦到这一点.不过,我已经在下面添加了一个关于如何做到这一点的说明.)

现在来看返回类型.对于这两个泛型参数和Pick实用程序类型,返回类型相对简单:Pick<InstanceType<Cls>, Method>.让我们把它定义为我们可以在不止一个地方使用的东西:

type WrapItReturn<Cls extends ConstructorFunction, Method extends keyof InstanceType<Cls>> = new (
    ...args: any
) => Pick<InstanceType<Cls>, Method>;

然后,我们使用WrapItReturn<Cls, method>作为返回类型(只是,我们可以让TypeScrip来推断它,但让我们显式一点),并将其作为返回内容的类型断言:

function wrapIt<Cls extends ConstructorFunction, Method extends keyof InstanceType<Cls>>(
    MyClass: Cls & { actions: readonly Method[] }
): WrapItReturn<Cls, Method> {
    return class {
        constructor() {
            for (const actionName of MyClass.actions) {
                const fn = MyClass.prototype[actionName];
                (this as any)[actionName] = (...args: any[]) => {
                    console.log("do something here that just call the original function");
                    return fn.call(this, ...args); // Minor implementation tweak here
                };
            }
        }
    } as WrapItReturn<Cls, Method>;
}

以下是所有这些内容:

type ConstructorFunction = new (...args: any[]) => any;

class Foo {
    static actions = ["func1", "func2"] as const;
    a!: number; // Added the ! just to avoid irrelevant initialization error, not part of solution
    func1(str: string) {
        console.log(str);
    }
    func2(n: number) {
        console.log(n);
    }
    func3() {}
}

type WrapItReturn<Cls extends ConstructorFunction, Method extends keyof InstanceType<Cls>> = new (
    ...args: any
) => Pick<InstanceType<Cls>, Method>;

function wrapIt<Cls extends ConstructorFunction, Method extends keyof InstanceType<Cls>>(
    MyClass: Cls & { actions: readonly Method[] }
): WrapItReturn<Cls, Method> {
    return class {
        constructor() {
            for (const actionName of MyClass.actions) {
                const fn = MyClass.prototype[actionName];
                (this as any)[actionName] = (...args: any[]) => {
                    console.log("do something here that just call the original function");
                    return fn.call(this, ...args); // Minor implementation tweak here
                };
            }
        }
    } as WrapItReturn<Cls, Method>;
}

const NewClass = wrapIt(Foo);

const c = new NewClass();
c.func1("ok"); // typescript error: func1 is not known

Playground link


如果要将actions的内容仅限于methods的关键点,则可以使用此实用程序类型:

type FunctionKeys<ObjType> = keyof {
    [Key in keyof ObjType as ObjType[Key] extends (...args: any[]) => any ? Key : never]: Key;
};

然后使用FunctionKeys<InstanceType<Cls>>而不是keyof InstanceType<Cls>:

type WrapItReturn<Cls extends ConstructorFunction, Method extends FunctionKeys<InstanceType<Cls>>> = new (
    ...args: any
) => Pick<InstanceType<Cls>, Method>;

function wrapIt<Cls extends ConstructorFunction, Method extends FunctionKeys<InstanceType<Cls>>>(
    MyClass: Cls & { actions: readonly Method[] }
): WrapItReturn<Cls, Method> {
    // ...
}

Playground link

在这种情况下,如果将"a"actions相加,您将看到Foo不再是wrapIt的有效参数,因为actions中列出了一个非函数属性.

Typescript相关问答推荐

调用另一个函数的函数的Typescript类型,参数相同

Typescript对每个属性具有约束的通用类型

如何在ts中使用函数重载推断参数类型

类型脚本接口索引签名

Angular 行为:属性类型从SignalFunction更改为布尔

具有继承的基于类的react 组件:呈现不能分配给基类型中的相同属性

如何消除在Next.js中使用Reaction上下文的延迟?

有没有可能产生输出T,它在视觉上省略了T中的一些键?

垫表页脚角v15

如何在React组件中指定forwardRef是可选的?

基于泛型的条件接口键

在Cypress中,是否可以在不标识错误的情况下对元素调用.Click()方法?

类型脚本-在调用期间解析函数参数类型

为什么在这种情况下,打字脚本泛型无法正确自动完成?

如何在try 运行独立的文字脚本Angular 项目时修复Visual Studio中的MODULE_NOT_FOUND

对于VUE3,错误XX不存在于从不存在的类型上意味着什么?

是否可以将类型参数约束为不具有属性?

如何在TypeScript中描述和实现包含特定属性的函数?

显式推断对象为只读

我可以在 Typescript 中重用函数声明吗?