TypeScript 允许非多态("私有")扩展基类

blmhpbnm  于 6个月前  发布在  TypeScript
关注(0)|答案(5)|浏览(65)

🔍 Search Terms

私有非多态不可赋值继承扩展子类更改基类的方法签名

✅ 可验证性清单

⭐ 建议

我想编写一个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);
  }
}

💻 用例

  1. 你打算为此使用什么?
    在我目前的项目中,我正在编写一个变量维数几何库,其中 Point 类是 Array<number> 的特化。Point是一个具有 Point<Dims extends number> 签名的泛型类,因此2D点是一个 Point<2> ,3D点是一个 Point<3> 等等。使用Array作为基类提供了许多优势,比如能够在基于现有的 .map() 创建具有相同维数的新Point。然而,大多数Array功能不应该向消费者公开;特别是,任何修改数组长度或创建不同长度的数组( pushslice 等)都会导致不正确的类型。
  2. 目前方法的缺点是什么?
    首先, .map() 的返回类型不正确,因为存在 Array inheritance in ES6 declaration file (lib.es6.d.ts) #10886;它和所有其他复制示例化方法应该返回一个 Point ,但实际上返回了 number[] 。由于 Provide declare keyword support for class methods #38008Allow 'declare' on methods and constructor #48290 的存在,我无法使用 declare 直接覆盖方法签名。由于类型不匹配,TS也无法允许我覆盖它作为属性。
  3. 在等待期间你正在使用什么解决方法?
    我完全删除了类型检查,通过将 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 注解,因为到目前为止,除了这个之外似乎没有其他解决方法。

wf82jlnq

wf82jlnq1#

这也可能与 #4628 有关。不是说我的这个问题受到静态继承的影响,而是因为如果这个问题通过添加类似于这里描述的 extends private 机制来解决,那么也可以用来解决 4628,通过允许分离继承成员和暴露成员。

2q5ifsrm

2q5ifsrm2#

我突然想到,解决这个问题的一个根本方法是添加一些语法,允许你完全抑制创建类的接口部分。然后你可以简单地单独定义接口,并暴露实际功能中你想要的部分,然后ES6类定义只是与该接口进行匹配,而不是生成接口本身。
这实际上感觉像是一个有用且可操作的功能。你可以像这样重写原始示例:

// This line is the only definition of the MyTuple<T> type
export interface MyTuple<T> extends ReadonlyArray<T> { }
// A private class is one whose types are not visible to the outside. It has signature { } or possibly object.
export private class MyTuple<T> extends Array<T>
// This one implements the MyTuple<T> interface to ensure that it matches its public API
    implements MyTuple<T> {
  constructor(...args: readonly T[]) {
    super();
    this.push(...args);
    Object.freeze(this);
  }
}

你怎么看?

sauutmhj

sauutmhj3#

你可以使用类表达式来使其"私有"。

// This line is the only definition of the MyTuple<T> type
export interface MyTuple<T> extends ReadonlyArray<T> { }
// A private class is one whose types are not visible to the outside. It has signature { } or possibly object.
export const MyTuple = class<T> extends Array<T>
// This one implements the MyTuple<T> interface to ensure that it matches its public API
    implements MyTuple<T> {
  constructor(...args: readonly T[]) {
    super();
    this.push(...args);
    Object.freeze(this);
  }
}

请注意,你绝对不能使用名称 MyTuple 作为类表达式。请使用其他名称或匿名表达式代替,否则在外部作用域中声明的接口 MyTuple<T> 将被遮蔽。

aydmsdu9

aydmsdu94#

是的,我之前使用过这个解决方法,但在这种情况下不适用。我需要emit包含一个ECMAScript顶级类导出,这与分配给const(或let、var等)的匿名类具有略微不同的语义。如果你有任何建议符合我的需要,我会洗耳恭听。

58wvjzkj

58wvjzkj5#

我正在寻找类似的内容,我想让我的子类成为一个只读数组。在尝试了所有的可能性后,我从StackOverflow和这个仓库的问题中拼凑出了这个东西。

function ReadonlyArrayCtor(): { new <T>(...items: readonly T[]): ReadonlyArray<T> } {
    return Array as any;
}

export class MyTuple<T> extends ReadonlyArrayCtor()<T> {
    constructor(...args: readonly T[]) {
        super(...args);
        Object.freeze(this);
    }
}

let vec = new MyTuple(1, 2);

vec[1] = 100; // error

Playground
这有点用。但无法将其变成一个实际的元组。即使将构造函数工厂转换为 readonly [number, number] 也无法限制子类只有两个索引。我猜 extends 会使类型扩展到至少有两个。

function ReadonlyArrayCtor(): { new (x: number, y: number): readonly [number, number] } {
    return Array as any;
}

export class MyTuple<T> extends ReadonlyArrayCtor() {
    constructor(x: number, y: number) {
        super(x, y);
        Object.freeze(this);
    }
}

let vec = new MyTuple(1, 2);

let x = vec[0]
let y = vec[1]
let z = vec[2] // no error

vec[1] = 100; // error

Playground

相关问题