typescript - 假运算符重载

标签 typescript

JS/TS 对StringNumberBoolean 类型进行自动装箱和拆箱,允许混合使用文字和对象在同一个表达式中,没有显式转换,例如:

const a = "3"+ new String("abc");

我正在尝试通过提供自定义类 Longbigintnumber 实现类似的功能:

class Long {
    public constructor(private value: bigint | number) { }

    public valueOf(): bigint {
        return BigInt(this.value);
    }
}

const long = new Long(123);
console.log(456n + long);

这工作得很好(并打印 579n),但会导致我的 linter 和 TS 编译器显示最后一个表达式的错误。我可以用这样的评论来压制它们:

// @ts-expect-error
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
console.log(456n + long);

但这对于整个应用来说并不是一个好的解决方案。

有没有办法告诉 Long 将被视为 bigint 或其他任何避免错误的方法?

关于为什么这样做:

我正在开发一种工具来转换 Java to Typescript,并希望支持尽可能多的 Java 语义。 Long 类型包含一个 long 整数,它是 64 位宽,在 TS 中只能使用 bigint 表示。这样做的主要问题是 Java 会自动拆箱 Long 就像 String 一样,我想尽可能地支持这种语义。

对于@caTS:所以这永远不会是普通的 TS 代码,而是始终用作 java.lang.Long,因此不会造成混淆。

最佳答案

您想要的行为,即 TypeScript 允许您使用自定义类 that overrides the Object.prototype.valueOf() method,就好像它是 valueOf() 返回的原始类型一样,不幸的是,这不是该语言的一部分。在 microsoft/TypeScript#2361 上有一个相当长的开放功能请求,但尚未实现,而且看起来不会很快实现。

目前,这意味着只有变通办法。您可以使用的一种解决方法是对编译器撒谎,说 Long 有一个返回原始实例的 construct signature。也就是说,您希望它具有 new (value: bigint | number) => bigint; 类型(或者 new (value: bigint | number) => bigint & Long code> 以便您保留添加到 Long 的任何额外方法或功能。

以下是您可以如何做到这一点:

// rename
class _Long { 
    public constructor(private value: bigint | number) { }

    public valueOf(): bigint {
        return BigInt(this.value);
    }
}

// assign and assert
const Long = _Long as 
    new (value: bigint | number) => bigint & _Long;

我在这里重命名了您原来的 Long 构造函数,因为一旦您声明了 class Long { }Long 就得到了自动构造一个构造函数类型,您不能更改该类型。

然后我将重命名的构造函数分配给名为 Long 的所需变量,并且 asserted 它是所需类型,new (value: bigint | number) => bigint & _Long 而不是实际类型,new (value: bigint | number) => _Long

我需要使用类型断言,因为编译器会提示简单的赋值;它知道 Long 的实例类型不是 bigint


好的,现在我们有一个名为 Long 的类构造函数,编译器认为它会生成 bigint 实例。让我们测试一下:

const long = new Long(123);
// const long: bigint
console.log(456n + long); // okay, 579

看起来不错。我可以调用 new Long(123),编译器认为结果是 bigint。它还让我可以毫无怨言地使用像 + 这样的数学运算符,结果就是您所期望的。


所以这和我想象的一样有效。尽管如此,我通常不会故意对编译器撒谎,因为这样的谎言以后可能会以奇怪的方式使您陷入困境。 long 的类型显然不是 bigint:

console.log(typeof long); // "object", not "bigint"

因此任何依赖于 long 的操作实际上是一个 bigint 可能会做一些编译器无法捕获的有趣事情,所以你会看到意想不到的运行时行为:

const bigint1 = BigInt(4);
const bigint2 = BigInt(4);
console.log(bigint1 === bigint2); // true

const long1 = new Long(4);
const long2 = new Long(4);
console.log(long1 === long2); // false!

function copy<T extends {}>(x: T) {
    return (typeof x === "object") ? { ...x } : x
}

const bigint3 = copy(bigint1);
console.log(bigint3 + 1n) // 5

const long3 = copy(long1);
console.log(long3 + 1n) // "[object Object]1" 🤪

现在在问题中提到的特定用例中,由于将生成所有 TypeScript 代码,因此您可以保证不会生成任何绊倒这些绊脚石的代码。但即便如此,了解这些事情并在决定是否要继续时将它们考虑在内也很重要。

Playground link to code

关于typescript - 假运算符重载,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/74144649/

相关文章:

javascript - ng2-bootstrap,从父组件调用子组件中定义的Modal

javascript - 我如何比较日期字符串?

javascript - 在页面 ionic 2 中使用 app.component 函数

javascript - 如果只有一个线程可用,async/await 有什么好处?

typescript - 如何使用ts在vue3中的render函数中公开组件方法

Javascript:类实例初始化与继承

javascript - 如何在重新加载页面后记住变量的值?

javascript - Typescript - 重新排列特定对象

html - 如何在 angular cli 中将 css 样式从 ts 导入到 less?

reactjs - 使用与 typescript react 的 Jest 测试复制到剪贴板方法