1

我有一个数据库模型(Mongo)和一个 API 模型(Apollo)。除了从字符串解析为对象的“开始”字段外,这两者是相同的。如何排除“开始”字段被继承或以某种方式覆盖它?

class Start {
  iso: string;
  local: string;
}

// database model
class BaseThing {
  // a lot of other fields
  start: string; // < - - I don't want to have 'any' type here
}

// API model
class Thing extends BaseThing {
  // a lot of other fields [inherited]
  start: Start; // < - - how to exclude start from being inherited or somehow override it?
}

4

1 回答 1

1

这不是一个很好的做法:子类继承超类的属性和方法。如果不需要继承,则可能真的需要组合而不是类层次结构,但这超出了这个问题的范围。让我们展示如何获得您正在寻找的行为,然后在最后提醒大家这可能会导致奇怪的问题。


如果您想防止编译器将BaseThing构造函数视为构造具有start属性的东西,您可以使用类型断言将类型BaseThing从(类似)扩大new () => BaseThingnew () => Omit<BaseThing, "start">使用Omit<T, K>实用程序类型

class Thing extends (BaseThing as new () => Omit<BaseThing, "start">) {
    start: Start = new Start(); // no error now
}

const thing = new Thing();
console.log(thing.start.iso.toUpperCase()) // ISO

这看起来有点难看,但它确实有效。编译器认为Thing继承了 startfrom之外的所有内容BaseThing

如果您经常做这种事情,您可以考虑将这种扩展重构为一个辅助函数,例如:

class Thing extends OmitCtorProp(BaseThing, "start") {
    start: Start = new Start(); // still okay
}

在哪里

const OmitCtorProp =
    <A extends any[], T extends object, K extends keyof T>(
        ctor: new (...a: A) => T, k: K) =>
        ctor as new (...a: A) => Omit<T, K>;

它可以隐藏在某个图书馆的某个地方。


请注意,根据基类的实现,这种故意扩大然后不兼容的重新缩小可能会导致麻烦。想象一下这样的事情:

class BaseOops {
    val: string = "a string"
    method() {
        console.log(this.val.toUpperCase())
    }
}

const baseOops = new BaseOops();
baseOops.method(); // "A STRING"

到目前为止,一切都很好。但是之后:

class SubOops extends OmitCtorProp(BaseOops, "val") {
    val: number = 12345;
}

const subOops = new SubOops();
subOops.method(); // RUNTIME ERROR!  this.val.toUpperCase is not a function

这失败了,因为基类method()依赖于valastring而不是其他任何东西。val更改为某些非类型的伪子类string将在运行时不愉快,除非它们也覆盖method(). 因此,任何故意绕过类型安全警告的人都应该注意不要绊倒问题已被扫过的地毯部分。

Playground 代码链接

于 2021-05-05T13:43:47.667 回答