我正在try 创建一个包装类.我有一个来自导入包的Base类,我想用一些日志(log)记录和Try/Catch来包装它,这样Wrapper类就可以从用户那里完全抽象出来,并且可以像使用Base类一样使用.到目前为止,我所拥有的是:

class Base {
    name: string;

    constructor(name: string) {
        this.name = name;
    }

    first() {}
    second(val: number) {}
    third(vals: number[]) {}
    ... // a lot of methods, can't map them manually
}

class Wrapper {
    bases: Base[] = [];

    constructor(names: string[]) {
        for (const name of names) {
            this.bases.push(new Base(name));
        }
    }
}

Object.getOwnPropertyNames(Base.prototype).forEach((methodName) => {
    if (methodName !== 'constructor') {
        Wrapper.prototype[methodName] = function (...args) {
            for (const base of this.bases) {
                try {
                    // logging 
                    return base[methodName].apply(base, args);
                } catch {}
            }
        };
    }
});

这样做的问题是我收到了一些类型警告:

  • No index signature with a parameter of type 'string' was found on type 'Wrapper'
  • No index signature with a parameter of type 'string' was found on type 'Base'

忽略这些,包装器类不会继承Base类型,所以如果我继承了

const wrapper = new Wrapper(['a', 'b', 'c']);
const s = wrapper.first();

我得Property 'first' does not exist on type 'Wrapper'分.

除了手动重写类型as unknown as Base之外,还有更好的方法来确保类型安全吗?我可以扩展Base类,尽管我会丢失一些类属性,比如本例中的name.

编辑:增加了更多Base类方法的例子.Base类非常大,所以有很多不同的方法,包括使用可选参数、重载等

推荐答案

假设我们更关心using Wrapper人的体验,而不是打字编译器能够验证我们的implemented Wrapper人是否正确,我倾向于这样进行:

首先,让我们确定需要将Base的哪些属性复制到Wrapper中.看起来这应该是所有的方法.类型脚本不能真正区分方法(在原型上)和函数值属性(在每个实例上)之间的区别,所以我希望您没有后者.我们可以使用key-remapped mapped type将对象的属性筛选为仅为函数属性:

type FuncsOf<T> = {
  [K in keyof T as T[K] extends Function ? K : never]: T[K]
}

然后,我们可以将FuncsOf<Base>转换为Wrapper实例类型:

interface Wrapper extends FuncsOf<Base> { }

这告诉TypeScrip,Wrapper实例也可以赋值给FuncsOf<Base>,而不需要或关心它实际上在任何地方实现.这有点危险,因为如果您忘记编写超过Base.prototype个条目的循环,或者如果您错误地编写了循环,编译器将不会知道或关心它.但我在这里并不担心这一点,因为正如我在顶部所说的那样,我们关注的是Wrapper的用户,而不是实现者.我们只需小心实现即可.

在这种情况下,我将使用the any typetype assertions来避免此循环中的任何编译器错误:

Object.getOwnPropertyNames(Base.prototype).forEach((methodName) => {
  if (methodName !== 'constructor') {
    (Wrapper.prototype as any)[methodName] = function (...args: any) {
      for (const base of this.bases) {
        try {
          // logging 
          return base[methodName].apply(base, args);
        } catch { }
      }
    };
  }
});

有一些方法可以开始try 编写,以便编译器更密切地判断循环,但坦率地说,这些方法带来的麻烦比它们的价值更大.(如果你有兴趣进入兔子洞,你可以看看microsoft/TypeScript#47109,以了解其中涉及的打字体操的种类.)


不管怎样,让我们试一试吧:

const wrapper = new Wrapper(['a', 'b', 'c']);
const s = wrapper.first(); // okay
wrapper.second(2); // okay
wrapper.third([1, 2, 3]); // okay

看上go 不错!

Playground link to code

Typescript相关问答推荐

如何使用属性作为具有Generics的TypScript类中的类型守护?

在这种情况下,如何获得类型安全函数的返回值?

typescribe不能使用值来索引对象类型,该对象类型满足具有该值的另一个类型'

如何为父类构造函数中的修饰属性赋值?

无法从Chrome清除还原的初始状态数据

如何在LucideAngular 添加自定义图标以及如何使用它们

为什么我的条件类型不能像我预期的那样工作?

限制返回联合的TS函数的返回类型

Angular:ngx-echart 5.2.2与Angular 9集成错误:NG 8002:无法绑定到选项,因为它不是div的已知属性'

按函数名的类型安全类型脚本方法包装帮助器

为什么TypeScrip不能解析对象析构中的赋值?

如何在Angular 服务中制作同步方法?

S,为什么我的T扩展未定义的条件在属性的上下文中不起作用?

在Reaction中折叠手风琴

如何在Deno中获取Base64图像的宽/高?

递归生成常量树形 struct 的键控类型

带有';的类型脚本嵌套对象可选属性错误满足';

是否使用类型将方法动态添加到类?

在Nestjs中,向模块提供抽象类无法正常工作

在Typescript 中,是否有一种方法来定义一个类型,其中该类型是字符串子集的所有可能组合?