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

Ros*_*ers 17 typescript

说我有两种类型的编号是我喜欢的跟踪latitudelongitude.我想用基本number原语表示这些变量,但不允许在typescript中将longitudea 赋值给latitude变量.

有没有办法对number基元进行子类化,以便打字稿将此作业检测为非法?在某种程度上强制名义输入,以便此代码失败?

var longitude : LongitudeNumber = new LongitudeNumber();
var latitude : LatitudeNumber;
latitude = longitude; // <-- type failure
Run Code Online (Sandbox Code Playgroud)

"如何在打字稿中扩展原始类型?"的答案 似乎它会让我朝着正确的方向前进,但我不知道如何扩展该解决方案以为不同类型的数字创建不同的名义子类型.

我必须包装原语吗?如果是这样,我可以使它表现得像普通数字一样无缝,还是我必须引用一个子成员?我可以以某种方式创建一个打字稿编译时数字子类吗?

geo*_*rch 12

使用Typescript 2.7 中引入的独特符号,实际上可以用两行代码很好地完成此操作:

\n
declare const latitudeSymbol: unique symbol;\nexport type Latitude = number & { [latitudeSymbol]: never };\n
Run Code Online (Sandbox Code Playgroud)\n

这样,Latitudes 就是numbers(并且可以像它们一样使用),但普通numbers 不是纬度。

\n

演示

\n
let myLatitude: Latitude;\nmyLatitude = 12.5 as Latitude; // works\nmyLatitude = 5; // error\nlet myOtherLatitude: Latitude = myLatitude // works\nlet myNumber: number = myLatitude // works\nmyLatitude = myNumber; // error\n\nconst added = myLatitude + myOtherLatitude; // works, result is number\n
Run Code Online (Sandbox Code Playgroud)\n

如果忽略第二行,错误消息大部分都很好:

\n
Type \'number\' is not assignable to type \'Latitude\'.\n  Type \'number\' is not assignable to type \'{ [latitudeSymbol]: never; }\'.ts(2322)\n
Run Code Online (Sandbox Code Playgroud)\n

评论

\n

声明unique symbol了一个新符号,我们需要将其作为 的属性Latitude。由于我们不导出该符号,因此无法访问它,因此对消费者来说是不可见的。

\n

这与biggle 的回答中的技术非常相似,只是它涵盖了评论中的反对意见:

\n
\n

唯一的问题是人们可能想要访问__opaque__ \xe2\x80\x93 Louis Garczynski

\n
\n

顺便说一句:如果你这样做,你就和一个很好的伙伴在一起了,ReactRedux正在使用类似的 hack。

\n


Lod*_*rds 11

以下是实现此目的的简单方法:

要求

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

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

用法

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

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;
}
Run Code Online (Sandbox Code Playgroud)

以下是如何使用LatitudeNumber的示例

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

doArithmeticAndLog(NumberType.to<LatitudeNumber>(100));
Run Code Online (Sandbox Code Playgroud)

这将登录200到控制台.

正如您所期望的那样,不能使用数字原语或其他数字类型调用此函数:

interface LongitudeNumber extends Number {
    LongitudeNumber;
}

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

这个怎么运作

这样做只是简单的傻瓜打字稿相信一个原始数字实际上是数字接口的一些扩展(我称之为数字类型),而实际上原始数字永远不会转换为实现数字类型的实际对象.转换不是必需的,因为数字类型的行为类似于原始数字类型; 数字类型只是一个数字原语.

诀窍是简单地转换为any,因此打字稿停止类型检查.所以上面的代码可以重写为:

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

doArithmeticAndLog(<any>100);
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,函数调用甚至不是必需的,因为数字及其数字类型可以互换使用.这意味着在运行时需要绝对零性能或内存丢失.我仍然强烈建议使用函数调用,因为函数调用的成本几乎为零,并且通过向any自己强制抛出类型安全性(例如,doArithmeticAndLog(<any>'bla')将编译,但会导致在运行时将NaN记录到控制台). ..但如果你想要完整的表现,你可以使用这个技巧.

它也可以用于其他原语,如字符串和布尔值.

打字快乐!


bin*_*les 5

您可以使用帮助程序类型在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;
Run Code Online (Sandbox Code Playgroud)

这是更详细的答案:https : //stackoverflow.com/a/50521248/20489

也是一篇有关执行此操作的不同方法的好文章:https : //michalzalecki.com/nominal-typing-in-typescript/