如何排除 class 字段被继承或以某种方式覆盖它?
How to exclude a class field from being inherited or somehow override it?
我有一个数据库模型 (Mongo) 和一个 API 模型 (Apollo)。除了从字符串解析为对象的 'start' 字段外,这两个是相同的。如何排除 'start' 字段被继承或以某种方式覆盖它?
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?
}
这不是很好的做法:subclasses 继承 superclass 属性和方法。如果不需要继承,那么可能真的需要 composition instead of class hierarchies,但这超出了这个问题的范围。让我们展示如何获得您正在寻找的行为,然后在最后提醒大家这可能会导致奇怪的问题。
如果你想阻止编译器将 BaseThing
构造函数视为使用 start
属性 构造的东西,你可以使用 type assertion to widen the type of BaseThing
from (something like) new () => BaseThing
to new () => Omit<BaseThing, "start">
using the Omit<T, K>
utility type:
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
视为从 BaseThing
.
继承所有 除了 start
如果你经常做这种事情,你可以考虑将这个扩展重构为一个辅助函数,比如:
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 的实施,这种有意加宽然后不兼容的重新缩小可能会导致麻烦。想象一下这样的事情:
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
这会失败,因为基础 class 的 method()
取决于 val
是 string
而不是其他任何东西。将 val
更改为某些非 string
类型的伪子 classes 将在运行时不愉快,除非它们也覆盖 method()
。因此,任何故意规避类型安全警告的人都应该注意不要被扫过问题的地毯部分绊倒。
我有一个数据库模型 (Mongo) 和一个 API 模型 (Apollo)。除了从字符串解析为对象的 'start' 字段外,这两个是相同的。如何排除 'start' 字段被继承或以某种方式覆盖它?
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?
}
这不是很好的做法:subclasses 继承 superclass 属性和方法。如果不需要继承,那么可能真的需要 composition instead of class hierarchies,但这超出了这个问题的范围。让我们展示如何获得您正在寻找的行为,然后在最后提醒大家这可能会导致奇怪的问题。
如果你想阻止编译器将 BaseThing
构造函数视为使用 start
属性 构造的东西,你可以使用 type assertion to widen the type of BaseThing
from (something like) new () => BaseThing
to new () => Omit<BaseThing, "start">
using the Omit<T, K>
utility type:
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
视为从 BaseThing
.
start
如果你经常做这种事情,你可以考虑将这个扩展重构为一个辅助函数,比如:
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 的实施,这种有意加宽然后不兼容的重新缩小可能会导致麻烦。想象一下这样的事情:
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
这会失败,因为基础 class 的 method()
取决于 val
是 string
而不是其他任何东西。将 val
更改为某些非 string
类型的伪子 classes 将在运行时不愉快,除非它们也覆盖 method()
。因此,任何故意规避类型安全警告的人都应该注意不要被扫过问题的地毯部分绊倒。