在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
中列出了一个非函数属性.