🔍 Search Terms
私有非多态不可赋值继承扩展子类更改基类的方法签名
✅ 可验证性清单
- 这不会在现有的TypeScript/JavaScript代码中引起破坏性的更改
- 这不会改变现有JavaScript代码的运行时行为
- 这可以通过不根据表达式的类型发射不同的JS来实现
- 这不是一个运行时特性(例如库功能,JavaScript输出的非ECMAScript语法,JS的新语法糖等)
- 这不是一个请求添加新实用类型的请求: https://github.com/microsoft/TypeScript/wiki/No-New-Utility-Types
- 这个特性将与我们设计目标的其他部分一致: https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals
⭐ 建议
我想编写一个ES6类,它不继承其基类的类型(因此不会暴露基类中定义的任何属性或方法,除非在子类中被覆盖)。在C++中,我会称之为“私有继承”,但简而言之:我希望能够使用类的 实现 (通过在方法中使用 super
关键字)而不声明我的示例是可赋值兼容的。
📃 动机示例
据我所知,目前没有在TypeScript中表达这种方式的方法。在纯JavaScript中,我可以编写以下代码:
export class MyTuple extends Array {
constructor(...args) {
super();
this.push(...args);
Object.freeze(this);
}
}
尽管扩展了 Array
原型, MyTuple
类的示例并不符合Array合同,因为所有的变异方法都会抛出TypeError。更糟糕的是,直接数组元素赋值会静默失败,因为对只读属性的赋值被忽略了。然而,使TypeScript输出此JS代码的唯一方法是使用TypeScript类声明,这些声明总是传播基类的类型。
在这个特定的情况下,该类对于 ReadonlyArray
接口来说是一个更好的匹配,因此理想情况下,我可以在TypeScript中编写以下内容:
export class MyTuple<T> extends private Array<T> implements ReadonlyArray<T> {
constructor(...args: readonly T[]) {
super();
this.push(...args);
Object.freeze(this);
}
}
💻 用例
- 你打算为此使用什么?
在我目前的项目中,我正在编写一个变量维数几何库,其中Point
类是Array<number>
的特化。Point是一个具有Point<Dims extends number>
签名的泛型类,因此2D点是一个Point<2>
,3D点是一个Point<3>
等等。使用Array作为基类提供了许多优势,比如能够在基于现有的.map()
创建具有相同维数的新Point。然而,大多数Array功能不应该向消费者公开;特别是,任何修改数组长度或创建不同长度的数组(push
、slice
等)都会导致不正确的类型。 - 目前方法的缺点是什么?
首先,.map()
的返回类型不正确,因为存在 Array inheritance in ES6 declaration file (lib.es6.d.ts) #10886;它和所有其他复制示例化方法应该返回一个Point
,但实际上返回了number[]
。由于 Providedeclare
keyword support for class methods #38008 和 Allow 'declare' on methods and constructor #48290 的存在,我无法使用declare
直接覆盖方法签名。由于类型不匹配,TS也无法允许我覆盖它作为属性。 - 在等待期间你正在使用什么解决方法?
我完全删除了类型检查,通过将Array
转换为Object
的虚拟构造函数:
export class Point<Dims extends number = 2> extends (Array as new () => Object) implements PointLike<Dims> {
以前我将其转换为 readonly number[]
的构造函数,但这不起作用,因为我需要声明 xm20n1x 作为返回 Point 的方法,而现有的 xm21n1x 返回签名无法转换为从 xm22n1x 派生的Point。因此现在我将其转换为Object,这带来了一个相当严重的缺点:我不能再使用 xm23n1x 关键字在我的函数中,因为TS认为 xm24n1x 是类型Object,而且我甚至无法纠正它-由于前面提到的 xe5f1x ,xm25n1x 关键字不能应用类型Assert。
现在我去写很多类型Assert和 xm26n1x 注解,因为到目前为止,除了这个之外似乎没有其他解决方法。
5条答案
按热度按时间wf82jlnq1#
这也可能与 #4628 有关。不是说我的这个问题受到静态继承的影响,而是因为如果这个问题通过添加类似于这里描述的
extends private
机制来解决,那么也可以用来解决 4628,通过允许分离继承成员和暴露成员。2q5ifsrm2#
我突然想到,解决这个问题的一个根本方法是添加一些语法,允许你完全抑制创建类的接口部分。然后你可以简单地单独定义接口,并暴露实际功能中你想要的部分,然后ES6类定义只是与该接口进行匹配,而不是生成接口本身。
这实际上感觉像是一个有用且可操作的功能。你可以像这样重写原始示例:
你怎么看?
sauutmhj3#
你可以使用类表达式来使其"私有"。
请注意,你绝对不能使用名称
MyTuple
作为类表达式。请使用其他名称或匿名表达式代替,否则在外部作用域中声明的接口MyTuple<T>
将被遮蔽。aydmsdu94#
是的,我之前使用过这个解决方法,但在这种情况下不适用。我需要emit包含一个ECMAScript顶级类导出,这与分配给const(或let、var等)的匿名类具有略微不同的语义。如果你有任何建议符合我的需要,我会洗耳恭听。
58wvjzkj5#
我正在寻找类似的内容,我想让我的子类成为一个只读数组。在尝试了所有的可能性后,我从StackOverflow和这个仓库的问题中拼凑出了这个东西。
Playground
这有点用。但无法将其变成一个实际的元组。即使将构造函数工厂转换为
readonly [number, number]
也无法限制子类只有两个索引。我猜extends
会使类型扩展到至少有两个。Playground