如何在TypeScript中为私有类属性实现类型守卫?
问题
我尝试创建一个带有私有可选属性的class,在类内部定义一个type guard来检查该属性是否不为undefined,同时希望有一个函数能处理该可选属性(若存在则返回)。以下是我的代码:
class DemoClass<X> { #val?: X constructor(val?: X) { this.#val = val } hasVal(): this is { val: X } { return this.#val !== undefined } extractVal(): X { if(this.hasVal()) { let val = this.#val! // let val = this.val works too // Manipulate val return val } throw new Error("Tried accessing val when it doesn't exist") } } let demoClassInstance = new DemoClass("Test") if(demoClassInstance.hasVal()) { console.log(demoClassInstance.extractVal()) console.log(demoClassInstance.val) // val is no longer private? }
问题在于,我不得不将私有属性定义为#val?: X而非private val: X,因为据我所知后者无法配合type guard工作。
而使用#val?: X时,我只能创建如下的type guard:
hasVal(): this is { val: X } { return this.#val !== undefined }
(我无法写成hasVal(): this is { #val: X }),这会导致本应私有的val属性被暴露。
因此我的问题是:在TypeScript中,为私有类属性实现type guard的正确方式是什么?
正确实现方式
要为私有字段实现类型守卫,不需要在类型断言里暴露公共属性,可通过以下两种方式实现,同时保持私有字段的封装性:
方法1:类内部定义交集类型守卫
利用类内部的私有类型,将this窄化为原类类型与包含确定私有字段的类型的交集,既完成类型断言,又不暴露私有字段:
class DemoClass<X> { #val?: X; constructor(val?: X) { this.#val = val; } // 定义仅类内可见的私有类型,标记#val为非可选 private type HasValInstance = this & { #val: X }; hasVal(): this is this.HasValInstance { return this.#val !== undefined; } extractVal(): X { if (this.hasVal()) { // 此处#val已被自动窄化为非undefined,无需非空断言 return this.#val; } throw new Error("Tried accessing val when it doesn't exist"); } } const demo = new DemoClass("Test"); if (demo.hasVal()) { console.log(demo.extractVal()); // 外部无法访问demo.val或demo.#val,私有性保持完好 }
方法2:拆分状态子类(更清晰的类型区分)
将类拆分为「有值」和「无值」两个子类,通过工厂方法创建实例,天然实现类型区分,同时无需额外的type guard逻辑:
abstract class BaseDemo<X> { abstract hasVal(): boolean; abstract extractVal(): X; } class DemoWithVal<X> extends BaseDemo<X> { #val: X; constructor(val: X) { super(); this.#val = val; } hasVal(): this is DemoWithVal<X> { return true; } extractVal(): X { return this.#val; } } class DemoWithoutVal<X> extends BaseDemo<X> { hasVal(): this is DemoWithoutVal<X> { return false; } extractVal(): X { throw new Error("Tried accessing val when it doesn't exist"); } } function createDemo<X>(val?: X): BaseDemo<X> { return val !== undefined ? new DemoWithVal(val) : new DemoWithoutVal<X>(); } const demo = createDemo("Test"); if (demo.hasVal()) { console.log(demo.extractVal()); // 外部同样无法访问私有字段 }
核心注意事项
- 禁止在type guard中声明公共属性(如
{ val: X }),这会直接破坏私有字段的封装性 - 私有字段
#val的类型窄化必须在类内部完成,利用TypeScript对类内部类型的访问权限 - 类型守卫的核心是窄化
this的类型,而非暴露内部私有成员
内容的提问来源于stack exchange,提问作者Riccardo Perego
相关产品推荐
相关产品推荐

