TypeScript 中的类封装函数
这是我的班级:
class Foo {
static actions = ["func1", "func2"];
a: number;
func1(str: string) {
};
func2(n: number) {
};
func3() {}
}
我的目标是为一个泛型函数(下面的 wrapIt 函数)做出正确的 TypeScript 注解,该函数接受任何类(例如 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 的 func1 和 func2 可能是原始的 func1 和 func2,但被一些代码包裹着
我用纯 JavaScript 编写这种代码没有困难,但在 TypeScript 中,对我来说简直是地狱 :-|
TS 游乐场
答案#1
你需要通过添加 as const 来将 Foo 中的 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 名称,只限制了实例属性名称。如果你不希望用户在 actions 中写 "a",你可以进一步处理,但我在这里没有处理。不过,我在下面包含了关于如何操作的说明。)
现在来说返回类型。使用这两个泛型参数和 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> 作为返回类型(仅此,我们可以让 TypeScript 推断它,但让我们显式指定)以及作为我们返回内容的类型断言:
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
游乐场链接
如果你想将 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> {
// ...
}
游乐场链接
在这种情况下,如果你向 actions 添加 "a",你会看到 Foo 不再是 wrapIt 的有效参数,因为一个非函数属性被列在了 actions 中。
