JS/TS对String
,Number
和Boolean
类型进行自动装箱和拆箱,允许在同一表达式中混合使用文字和对象,而无需显式转换,例如:const a = "3" + new String("abc");
我尝试通过提供一个定制类Long
来为bigint
和number
实现类似的功能:
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语义。其只能通过使用bigint
在TS中表示。主要问题是Java会像String
一样自动解盒Long
,我希望尽可能地支持这种语义。
对于@caTS:所以这永远不会是正常的TS代码,但总是作为java.lang.Long
使用,因此不会有混淆。
2条答案
按热度按时间0yg35tkg1#
你想要的行为,即TypeScript允许你使用一个自定义类来覆盖
Object.prototype.valueOf()
方法,就像它是valueOf()
返回的原始类型一样,不幸的是,它不是语言的一部分。在microsoft/TypeScript#2361上有一个相当长的开放特性请求,但是它还没有被实现,而且看起来也不会很快被实现。目前,这意味着只有一些变通方法,其中一个变通方法是对编译器撒谎,说
Long
有一个返回原语示例的构造签名,也就是说,你希望它具有new (value: bigint | number) => bigint;
类型(或者new (value: bigint | number) => bigint & Long
,这样你就可以保留添加到Long
的任何额外方法或功能)。以下是您的具体做法:
这里我把你原来的
Long
构造函数重命名了,因为一旦你声明了class Long { }
,值Long
就会自动得到一个构造函数类型,你不能改变这个类型。然后,我将重命名的构造函数赋给所需的变量
Long
,并Assert它是所需的类型new (value: bigint | number) => bigint & _Long
,而不是实际的类型new (value: bigint | number) => _Long
。我需要使用类型Assert,因为编译器会抱怨普通赋值;它知道X1 M12 N1 X的示例类型不是X1 M13 N1 X。
好了,现在我们有了一个名为
Long
的类构造函数,编译器认为它产生了bigint
示例。让我们测试一下:看起来不错。我可以调用
new Long(123)
,编译器认为结果是bigint
。它还让我毫无怨言地使用像+
这样的数学运算符,结果也是你所期望的。所以这和我想象的差不多。不过,我通常不会故意对编译器撒谎,因为这样的谎言会在以后以奇怪的方式绊倒你。
long
的类型显然 * 不是 *bigint
:因此,任何依赖于
long
实际上是bigint
的操作都可能会执行编译器无法捕捉的有趣操作,因此您将看到意外的运行时行为:现在,在问题中提到的特定用例中,由于将生成所有TypeScript代码,因此您可以保证不会生成任何代码,这些代码会遇到任何障碍。但即使如此,在决定是否继续时,了解这些情况并将其考虑在内是很重要的。
Playground代码链接
ryoqjall2#
除了@jcalz已经提到的问题之外,他的解决方案还有一个大问题:您不能使用
Long
类的任何功能。对于编译器和ESLint,由于附加的构造函数Assert,Long
被视为bigint
基元类型。然而,JS有一个内置的原语解析方法,在Symbol.toPrimitive页面上有描述。在我最初的方法中,我使用了一个解决方案(
valueOf
),让JS自动将Long
类转换为原语bigint
值,这保留了Long
类的所有功能。不幸的是,使用
valueOf
或Symbol.toPrimitive
仍然需要一个显式步骤(调用数值、字符串或原语强制),因此这只是一个半完美的解决方案。(playground)。注意
long
变量前面的额外+
,它触发了数字强制。正如您所看到的,我在这里没有使用bigint
,因为它以不同的方式处理。在原始代码中,不需要数字强制来使它工作(只有错误抑制)。但是,将Symbol.toPrimitive
与bigint
一起使用是行不通的,即使使用数字强制也是如此。它只是没有实现。一个添加该功能的建议是refused years ago。所以,总的来说,对于
bigint
,我的问题没有解决方案(而且bigint
也不是一个好的选择)。但是对于所有其他可以强制转换为基元类型(字符串或数字)的类,如果你接受显式强制,上面提到的方法可以很好地工作。更新
我考虑了@jcalz的建议,为
Long
类使用一个单独的交集类型。查看这个新的playground示例以了解详细信息。虽然这看起来很有效,但它仍然显示了一些打字脚本错误,我不得不抑制这些错误。基本上所有静态成员都会引发TS错误。此外,这个解决方案还有其他问题,比如在使用
constructor.name
(我必须使用它来进行反射仿真)时更改了类名(不再是LONG
)。