网站搜索

在 TypeScript Mixin 之后重写抽象 getter


我有一个应用程序,使用了一些带有 Mixin 模式的通用类,并且正在尝试实现一个属性 getter,这个 getter 对于 90% 的 Mixin 用户来说是一样的,但并非完全相同…

我惊讶地发现:

  1. 可以定义抽象方法,在混入中实现它们,并在子类中重写它们
  2. 在 mixin 中定义新的属性 getter 并在子类中重写它们是可以的
  3. 但是,尝试在 mixin 中定义抽象属性的 getter,实现 它们,并在子类中重写它们——会引发错误吗?

并且当我尝试在子类中重写混入中定义的属性获取器时,发现了一个意想不到的问题。

下面是一个小的代码片段,试图演示所有三种行为——有人能帮我理解为什么第3点是一个错误,以及是否有好的方法来修复它吗?我希望保持 TextThing 的任何子类被强制实现抽象属性的安全性……

/**
 * Basic interface objects should implement
 */
export interface ITextThing<T> {
  dataSource: T;
  html(): string;
  get text(): string;
}

/**
 * Abstract base class with partial implementation
 */
export abstract class TextThing<T> implements ITextThing<T> {
  dataSource: T;

  constructor(data: T) {
    this.dataSource = data;
  }

  abstract html(): string;
  abstract get text(): string;
}

type AbstractOrConcreteConstructor<T> = (new (...args: any[]) => T) | (abstract new (...args: any[]) => T);

/**
 * A mixin with extra functionality
 */
export function WithCounter<T extends AbstractOrConcreteConstructor<{}>>(SuperClass: T) {
  return class extends SuperClass {
    _counter: number;

    constructor(...args: any[]) {
      super(...args);
      this._counter = 0;
    }

    get count(): number {
      return this._counter;
    }

    get text(): string {
      return `[${this._counter}]`;
    }
  };
}

/**
 * The problem class: Using the mixin...
 */
export class TextWithCounter<TData> extends WithCounter(TextThing)<TData> {
  constructor(data: TData, initialCount = 0) {
    super(data);
    this._counter = initialCount;
  }

  // Overriding a getter defined in the mixin seems fine...
  override get count(): number {
    return this._counter + 1;
  }

  // Overriding a base class abstract function implemented in mixin seems fine...
  html(): string {
    return `<p><strong>${this.count}: </strong>${this.dataSource}</p>`;
  }

  // But base class abstract getter implemented in the mixin throws an error?
  //  > 'text' is defined as a property in class 'WithCounter<typeof TextThing>.(Anonymous class)
  //     & TextThing<TData>', but is overridden here in 'TextWithCounter<TData>' as an
  //     accessor.ts(2611)
  override get text(): string {
    return `[${this.count}]: ${this.dataSource}`;
  }
}

这个项目目前使用的是 TypeScript v4.5.4,但如果升级可能会有帮助?我不太确定这是一个错误/限制,还是我们代码的问题……

答案#1

我认为这是由这个错误引起的。我认为 getter 通常被视为只读属性,这在像这样继承时会成为一个问题。然而,方法可以正常工作,所以解决这个问题的一种方法可能是将 get text() 的声明和定义替换为 getText(),然后在抽象类中添加 get text() 的具体实现,该实现再调用 getText()。所以 TextThing 变成:

export abstract class TextThing<T> implements ITextThing<T> {
    dataSource: T;

    constructor(data: T) {
        this.dataSource = data;
    }

    abstract html(): string;
    abstract getText(): string;
    get text(): string {
        return this.getText();
    }
}