是否有用于将 class 属性 限制为整数的类型保护语法?

Is there a typeguard syntax for restricting a class property to integer?

我有一个使用大量货币值的应用程序,还有几个 类 反映了 'money' 属性。

我计划将所有内容都存储为整数,并且只在视图中转换为十进制表示形式。

我可以使用一些 属性 声明保护语法来确保在编译时尝试分配一个不能确定不是整数的常规数字时会标记错误到这些领域之一?

例如:

 class Account {
     name: string
     private balance : integer = 0;
     maxWithdrawal: integer = 0;
     maxOverdraft : integer = 0;
     ...

     deposit(amount : number) {
         this.balance += deposit;   // flags some kind of "deposit may not be an integer" error.
     }

 }

不,TypeScript 中没有将值限制为整数的内置类型。 因此,无法在编译时捕获此类错误。

如果需要,您可以在 运行 时间检查整数。 您可以创建自己的 "boxed" 整数,并使用 Number.isInteger() 确保装箱值是整数。例如:

class BoxedInteger {
  public readonly value: number;

  public constructor(value: number) {
    if (!Number.isInteger(value)) {
      throw new Error(`Number is not an integer: ${value}`);
    }
    this.value = value;
  }
}

const a = new BoxedInteger(5).value; // ok, a = 5
const b = new BoxedInteger(5.5).value; // error!

[*] 注意: 请记住 Number.isInteger() 的限制 例如:Number.isInteger(5.0000000000000001); // true

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isInteger

BigInt 目前是唯一的整数类型(除了像 1|2|3 这样的固定集合)。什么 TS 维护者 say:

BigInt seems like a "close enough" fit for these use cases and doesn't require us inventing new expression-level syntax.

这里有两种可能的解决方案:

1.) BigInt

class AccountBigInt {
  private balance: bigint = 4n;

  deposit(deposit: bigint) {
    this.balance += deposit;  
  }
}

const a = new AccountBigInt()
a.deposit(3n)
// a.deposit(3.3n) // error
// a.deposit(3.3)  // error

2.) Branded Integer 类型

type Integer = number & { __brand__: "__integer__" } // branded primitive type

// use this as the only way / factory method to create an Integer type
const int = (val: number): Integer => {
  if (Number.isInteger(val)) return val as unknown as Integer
  throw Error("val is not an integer.")
}

const i1 = int(3)
const i2 = int(3.3) // throws at run-time
const sum = int(3) + int(4) // works, Integer is a subtype of number

class AccountBranded {
  private balance: Integer = int(4);

  deposit(deposit: Integer) {
    // wrap with int(), because this.balance + deposit returns number
    // you also could create an add function for Integer
    this.balance = int(this.balance + deposit)
  }
}

const a2 = new AccountBranded()
a2.deposit(int(3)) // works
a2.deposit(3) // error, number not assignable to Integer (OK)

Code sample


更新:BigInt 支持

目前,BigInt isn't adopted widely (2020-01-10)。据我所知,那里没有“真正的” polyfill 库——它必须为 +- 等运算符更改核心 JS 逻辑。这将是一个相当复杂的方法!

一个well-known approach is to use JSBI library (see also here). Once BigInt support is better, you can transpile back to native BigInt without rewriting your code using a babel plugin.