当前位置: 首页 > 知识库问答 >
问题:

有没有办法在TypeScript中创建扩展基元类型的名义类型?

沈永新
2023-03-14

假设我有两种我正在跟踪的数字,比如纬度经度。我想用基本的数字原语来表示这些变量,但不允许在typescript中将经度赋值给纬度变量。

有没有办法对数字原语进行子分类,以便typescript检测到该赋值是非法的?以某种方式强制名义键入,使此代码失败?

var longitude : LongitudeNumber = new LongitudeNumber();
var latitude : LatitudeNumber;
latitude = longitude; // <-- type failure

“如何在typescript中扩展基元类型”的答案看起来这会让我走上正确的方向,但我不确定如何扩展该解决方案,为不同类型的数字创建不同的命名子类型。

我必须包装基元吗?如果是这样,我能让它像普通数字一样无缝地运行吗?或者我必须引用子成员?我可以创建一个打字稿编译时数字子类吗?

共有3个答案

方献
2023-03-14

实际上,有一种方法可以实现您想要实现的目标,但它有点棘手,有一些限制,对于第一次看到该代码的人来说可能是完全不合理的,所以请将其视为一种好奇心,而不是实际的实现;)

好的,我们走吧。首先,我们需要创建一个Number的“子类”。问题是,这个lib。d、 ts实际上将Number声明为一个接口,而不是一个类(这是合理的-不需要实现方法,浏览器会处理这个问题)。因此,我们必须实现接口声明的所有方法,谢天谢地,我们可以使用已声明的varNumber的现有实现。

class WrappedNumber implements Number {
    //this will serve as a storage for actual number
    private value: number;

    constructor(arg?: number) {
        this.value = arg;
    }

    //and these are the methods needed by Number interface
    toString(radix?: number): string {
        return Number.prototype.toString.apply(this.value, arguments);
    }

    toFixed(fractionDigits?: number): string {
        return Number.prototype.toFixed.apply(this.value, arguments);
    }

    toExponential(fractionDigits?: number): string {
        return Number.prototype.toExponential.apply(this.value, arguments);
    }

    toPrecision(precision: number): string {
        return Number.prototype.toPrecision.apply(this.value, arguments);
    }

    //this method isn't actually declared by Number interface but it can be useful - we'll get to that
    valueOf(): number {
        return this.value;
    }
}

现在,我们创建了一个类型WrappedNumber,其行为与数字类型类似。您甚至可以添加两个WrappedNumbers,这要归功于valueOf()方法。但是,这里有两个限制:首先,您需要强制转换变量来执行此操作。第二:结果将是一个常规的数字,因此之后应该再次包装它。让我们看一个加法的例子。

var x = new WrappedNumber(5);
var y = new WrappedNumber(7);

//We need to cast x and y to <any>, otherwise compiler
//won't allow you to add them
var z = <any>x + <any>y;

//Also, compiler now recognizes z as of type any.
//During runtime z would be a regular number, as
//we added two numbers. So instead, we have to wrap it again
var z = new WrappedNumber(<any>x + <any>y); //z is a WrappedNumber, which holds value 12 under the hood

在我看来,这是最棘手的部分。现在我们创建了两个类,LatitudeLongitude,它们将继承自WrappedNumber(这样它们就可以作为数字)

class Latitude extends WrappedNumber {
    private $;
}
class Longitude extends WrappedNumber {
    private $;
}

搞什么鬼?TypeScript在比较类型时使用duck类型。这意味着,当两种不同类型具有相同的属性集时,它们被视为“兼容”(因此可分配给其自身,即您可以将一种类型的变量分配给另一种类型的值)。解决方案非常简单:添加一个私有成员。这个私有成员是纯虚拟的,它不在任何地方使用,也不会被编译。但它使TypeScript认为纬度经度是完全不同的类型,我们更感兴趣的是,不允许将经度类型的变量分配给纬度类型的变量。

var a = new Latitude(4);
var b: Longitude;
b = a; //error! Cannot convert type 'Latitude' to 'Longitude'

瞧!这就是我们想要的。但是代码很混乱,您需要记住强制转换类型,这非常不方便,所以实际上不要使用它。然而,正如你所看到的,这是可能的。

戴鸿羲
2023-03-14

以下是实现这一目标的简单方法:

您只需要两个函数,一个用于将数字转换为数字类型,另一个用于反向处理。以下是两个功能:

module NumberType {
    /**
     * Use this function to convert to a number type from a number primitive.
     * @param n a number primitive
     * @returns a number type that represents the number primitive
     */
    export function to<T extends Number>(n : number) : T {
        return (<any> n);
    }

    /**
     * Use this function to convert a number type back to a number primitive.
     * @param nt a number type
     * @returns the number primitive that is represented by the number type
     */
    export function from<T extends Number>(nt : T) : number {
        return (<any> nt);
    }
}

您可以创建自己的数字类型,就像这样:

interface LatitudeNumber extends Number {
    // some property to structurally differentiate MyIdentifier
    // from other number types is needed due to typescript's structural
    // typing. Since this is an interface I suggest you reuse the name
    // of the interface, like so:
    LatitudeNumber;
}

下面是一个如何使用LatitudeNumber的例子

function doArithmeticAndLog(lat : LatitudeNumber) {
    console.log(NumberType.from(lat) * 2);
}

doArithmeticAndLog(NumberType.to<LatitudeNumber>(100));

这将日志200到控制台。

如您所料,此函数不能使用数字原语或其他数字类型调用:

interface LongitudeNumber extends Number {
    LongitudeNumber;
}

doArithmeticAndLog(2); // compile error: (number != LongitudeNumber)
doArithmeticAndLog(NumberType.to<LongitudeNumber>(2)); // compile error: LongitudeNumer != LatitudeNumber

这只不过是愚弄Typescript,使其相信一个原语数字实际上是数字接口的某个扩展(我称之为数字类型),而实际上原语数字永远不会转换为实现数字类型的实际对象。不需要转换,因为数字类型的行为类似于原始数字类型;数字类型只是一个数字原语。

诀窍是简单地转换为any,以便typecript停止类型检查。所以上面的代码可以改写为:

function doArithmeticAndLog(lat : LatitudeNumber) {
    console.log(<any> lat * 2);
}

doArithmeticAndLog(<any>100);

正如您所见,函数调用甚至不是真正必要的,因为数字及其数字类型可以互换使用。这意味着在运行时需要产生绝对零的性能或内存损失。我仍然强烈建议使用函数调用,因为函数调用的成本几乎为零,并且通过强制转换到any您自己,您就失去了类型安全性(例如doarithmetricandlog(

它还可以用于其他基本体,如字符串和布尔值。

打字快乐!

仲孙诚
2023-03-14

您可以使用助手类型近似Typescript中的不透明/标称类型。有关更多详细信息,请参阅此答案:

// Helper for generating Opaque types.
type Opaque<T, K> = T & { __opaque__: K };

// 2 opaque types created with the helper
type Int = Opaque<number, 'Int'>;
type ID = Opaque<number, 'ID'>;

// works
const x: Int = 1 as Int;
const y: ID = 5 as ID;
const z = x + y;

// doesn't work
const a: Int = 1;
const b: Int = x;

// also works so beware
const f: Int = 1.15 as Int;

下面是一个更详细的答案:https://stackoverflow.com/a/50521248/20489

还有一篇关于不同方法的好文章:https://michalzalecki.com/nominal-typing-in-typescript/

 类似资料:
  • 我想创建如下界面: 然后我们可以将它用于一个新类: 我想对s做同样的事情。在纯JavaScript中,我可以使类型,例如,满足以下接口: 但TypeScript不允许我这样做: 有办法做到这一点吗?

  • 我有一个非常简单的练习,我注册客户端名称(字符串),我使用数组来做到这一点,所以当我添加一个新的我将使用另一个辅助数组,然后增加原来的长度,或者我将使用System.array复制,有没有其他方法,逐渐增加数组长度,因为你需要添加元素到该数组,不涉及辅助数组?

  • 问题内容: 假设我有以下情况: 有没有一种方法可以保证实现的任何类也必须扩展?我不想创建一个抽象类,因为我希望能够以类似的方式混合其他一些接口。 例如: 问题答案: Java接口无法扩展类,这很有意义,因为类包含无法在接口内指定的实现细节。 解决此问题的正确方法是通过将接口也完全从实现中分离出来。所述等可以扩展接口以迫使程序员来实现相应的方法。如果要在所有实例之间共享代码,则可以将(可能是抽象的)

  • 假设有一个类库X的输入文件,其中包含一些接口。 为了使用这个库,我需要传递一个与。当然,我可以在源文件中创建相同的接口: 但这样一来,我就有了让它和库中的一个保持最新的负担,而且它可能非常大,并导致大量代码重复。 因此,有没有办法“提取”接口的这个特定属性的类型?类似于(这不起作用,导致“找不到名称I2”错误)。 编辑:在TS操场上玩了一会儿后,我注意到以下代码完全实现了我想要的: 但是,它需要声

  • 我在typescript中有以下泛型类 但是我不知道为什么得到这个错误Class'(匿名类)'不正确地扩展基类'列'。属性getValue的类型不兼容。类型'(值:数字)=

  • 我有一个打字错误310.4。1在Ubuntu20.04上安装(composer模式),使用PHP7.4和mariadb数据库,并安装扩展名“extension Builder”(v10兼容性)。当我使用扩展生成器使用域模型创建一个新的扩展并保存它时,将其添加到编写器时,也不会出现错误。json并运行composerrequire命令。但是,数据库中没有创建表。 有人知道从哪里开始寻找问题吗? 提前