TypeScript类实现具有私有函数的类

21

我正在探索使用TypeScript实现一个类的可能性。

因此,我编写了以下代码Playground链接

class A {
    private f() { console.log("f"); }
    public g() { console.log("G"); }
}

class B implements A {
    public g() { console.log("g"); }
}

我遇到了错误:Class 'B' incorrectly implements class 'A' --- property 'f' is missing in type 'B',并且建议我实际上是想使用extends

因此我尝试创建一个名为f的私有字段(public无法识别它们具有不同的访问修饰符)Playground链接

现在我收到错误: Class 'B' incorrectly implements class 'A'. Types have separate declarations of a private property 'f'。这让我很困惑:

  • 为什么私有成员如此重要-如果我使用不同的数据结构实现相同的算法,我是否必须声明相同的内容,以便进行类型检查?
  • 为什么当我将f实现为私有函数时会出现错误?

实际上我不会在实践中这样做,但是我对TS为什么会像这样工作感到好奇。

谢谢!


我正在探索在类上使用 implements 的行为。@Igor,我不想那样 :) - Radu Szasz
好的。这是否对您的问题有所启示?在TypeScript中扩展与实现纯抽象类 - Igor
@Igor 不,因为我已经在我要实现的类中实现了所有的函数(请参阅第二个Playground链接)。 - Radu Szasz
1
您可能也对此问题感兴趣。 - CRice
4个回答

36
这个问题Microsoft/TypeScript#18499讨论了为什么在确定兼容性时需要私有成员。原因是:类的私有成员对同一类的其他实例是可见的
@RyanCavanaugh的一个remark特别相关和启示性:
允许缺少私有字段将是一个巨大的问题,而不是一些微不足道的正确性问题。
考虑以下代码: class Identity { private id: string = "secret agent"; public sameAs(other: Identity) { return this.id.toLowerCase() === other.id.toLowerCase(); } } class MockIdentity implements Identity { public sameAs(other: Identity) { return false; } } MockIdentityIdentity的公共兼容版本,但试图将其用作一个会导致在非模拟副本与模拟副本交互时,在sameAs中崩溃。
只是为了清楚起见,这里是它失败的地方:
const identity = new Identity();
const mockIdentity = new MockIdentity();
identity.sameAs(mockIdentity); // boom!

所以,你不能做这件事情有很好的理由。


作为一种解决方法,您可以按照以下方式提取具有映射类型的类的公共属性:{{code}}。
type PublicPart<T> = {[K in keyof T]: T[K]}

然后你可以让B实现的不是A,而是PublicPart<A>

class A {
    private f() { console.log("f"); }
    public g() { console.log("G"); }
}

// works    
class B implements PublicPart<A> {
    public g() { console.log("g"); }
}

希望这有所帮助;祝好运!


1
我认为这个例子有点不诚实,other.idsameAs 方法中是无法访问的,对吗? - nomadoda
1
如果您认为所编写的Identity类无效,您可以自行测试并查看其确实有效。具有私有属性的类的实例可以访问同一类的其他实例上的该属性。这就是TS中private的工作原理,也是JS即将推出的私有属性语法的工作方式(我想您可以在Chrome中自行测试)。因此,请放心,这里的示例是真诚的。干杯! - jcalz
2
“一个拥有私有属性的类的实例可以访问同一类其他实例上的该属性” - 很有趣,我不知道这个。谢谢。 - nomadoda
我也有同样的想法,TypeScript规范对“private”的解释让我感到相当奇怪。 - RocketMan
1
这种私有访问在大多数编程语言中都是相同的。然而,ECMAScript(JavaScript)正在引入带有#前缀的私有类字段,实际上这些字段无法被同一类的实例访问。这是世界上的第一次尝试吗?[1] [2] - Lars Gyrup Brink Nielsen
@LarsGyrupBrinkNielsen #-私有字段在可访问性方面与private相同:类Foo的实例确实能够访问其他类Foo实例的私有成员。如果您的运行时环境支持私有字段(我认为Chrome支持),您可以在此处自行检查here。TypeScript的实现也是如此;请参见here - jcalz

6

目前Typescript的开箱即用支持的解决方案非常简单

class A {
    private f() { console.log("f"); }
    public g() { console.log("G"); }
}

class B implements Pick<A, keyof A> {
    public g() { console.log("g"); }
}

解释: keyof A 仅返回 A 的公共属性(和方法),Pick 然后会将 A 下降修剪为仅其公共属性及其相应类型。


是的,这应该是一种实用程序类型! - CMCDragonkai

2

1
谢谢!目前似乎TS编译器不允许这样做。我认为在这种情况下,完全禁用实现类应该是最好的选择。 - Radu Szasz
@RaduSzasz - 正确。看了一下 GitHub 的问题,你不是第一个遇到这个问题的人,我也怀疑你不会是最后一个。希望他们能够以某种方式解决它(使其成为可能或创建更好的错误消息)。 - Igor

1
这主要是因为私有成员的可见性是针对类型而非实例的,这意味着类型T的所有对象都可以访问其他类型T对象的私有成员。在名称化类型语言中,这不是问题,因为T的所有实例都继承了T的实现,但由于TypeScript是结构化类型的,这意味着我们不能假设满足T的所有实例都具有声明类型T的类的实现。这意味着私有作用域成员必须成为类型的公共合同的一部分,否则结构类型T的对象可能会调用另一个具有相同结构类型的对象的不存在的私有成员。被迫将私有成员作为公共类型合同的一部分是不好的,可以通过将私有成员作用域限定为实例而不是类型来避免这种情况。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接