有没有办法在TypeScript中表示非负整数,以便编译器可以防止使用分数和负数?

Tri*_*Gao 8 type-safety typescript

这个数字是一个非常松散的表示,有时需要收紧.在我的情况下,我希望变量只能采用非负整数.有没有办法在TypeScript中强制执行此约束?

Hun*_*ler 52

2021 年更新:

\n

是的,模板文字允许这样做;观察:

\n
type NonNegativeInteger<T extends number> =\n    number extends T \n        ? never \n        : `${T}` extends `-${string}` | `${string}.${string}`\n            ? never \n            : T;\n
Run Code Online (Sandbox Code Playgroud)\n

请注意,number extends T有必要限制一般number type.

\n

用法:

\n
function negate<N extends number>(n: NonNegativeInteger<N>): number {\n    return -n;\n}\n\n\nnegate(3); // success\nnegate(3.1); // failure\nnegate(-3); // failure\nnegate(-3.1); // failure\n
Run Code Online (Sandbox Code Playgroud)\n

用法

\n

回复 @ianstarz 评论:

\n
\n

您将如何在类字段或变量类型上使用它?myField: NonNegativeInteger = 42 似乎不起作用\xe2\x80\x94I\不确定在这种情况下要作为泛型类型传递什么。您能否提供一个示例来说明如何在这种情况下使用泛型?

\n
\n

了解在 Typescript 中,文字本身被视为类型;示例:10可分配给 some let x: number,但只能10分配给 some let x: 10。此外,Typescript 拥有强大的类型推断系统,但它也只能做到这一点,否则就会成为开发的负担。上述类型的目标是执行以下两件事之一:

\n
    \n
  1. 限制函数的文字参数。
  2. \n
  3. 应用进一步的类型操作。
  4. \n
\n

您的问题不仅适用于类字段,也适用于上述类型。Typescript 变量在声明时应用类型推断,而不是赋值;此推论不会扩展到变量的泛型。

\n

为了演示泛型变量类型和函数调用之间的区别,请考虑使用泛型标识类型时的以下错误:

\n
type Identity<T> = Identity;\n\n// Generic type \'Example\' requires 1 type argument(s)\nlet x: Identity = 10;\n
Run Code Online (Sandbox Code Playgroud)\n

相比:

\n
type Identity<T> = Identity;\n\nfunction identity<T>(x: Identity<T>): T {\n    return x;\n}\n\nlet y = identity(10); // Success, y has type `number`\nconst z = identity(10); // Success, z has type `10`\n
Run Code Online (Sandbox Code Playgroud)\n

请注意如何z假定文字类型。事实上,我们可以显式输入y相同的内容,但它只允许10作为一个值,而不是任何其他数字。

\n

有限文字联合

\n

如果您有有限数量的整数值(例如文件描述符),请创建一个具有如下类型的字段:

\n
type EvenDigit = 0 | 2 | 4 | 6 | 8;\n\nlet x: EvenDigit = 2; // Success\nlet y: EvenDigit = 10; // Failure\n
Run Code Online (Sandbox Code Playgroud)\n

如果您很疯狂,请编写一个生成联合类型的脚本。请注意,联合类型的成员数量可能存在特定于版本的上限。

\n

计算文字并集

\n

如果你想要超级元,类似这样的东西会生成一系列类型:

\n
// Assumes, for simplicity, that arguments Start and End are integers, and\n// 0 < Start < End.\n// Examples:\n// Range<0, 5> -> 0 | 1 | 2 | 3 | 4 | 5\n// Only can calculate so much:\n// Range<0, 100> -> \'Type instantiation is excessively deep and possibly infinite.ts(2589)\'\n// Tail end recursion being introduced in Typescript 4.5 may improve this.\ntype Range<Start extends number, End extends number> = RangeImpl<Start, End>;\ntype RangeImpl<\n    Start extends number,\n    End extends number,\n    T extends void[] = Tuple<void, Start>\n> = End extends T["length"]\n    ? End\n    : T["length"] | RangeImpl<Start, End, [void, ...T]>;\n\n// Helper type for creating `N` length tuples. Assumes `N` is an integer\n// greater than `0`. Example:\n// Tuple<number, 2 | 4> -> [number, number] | [number, number, number, number]\ntype Tuple<T, N extends number> = TupleImpl<T, N>;\n// prettier-ignore\ntype TupleImpl<T, N extends number, U extends T[] = []> =\n    N extends U["length"]\n        ? U\n        : TupleImpl<T, N, [T, ...U]>;\n\n
Run Code Online (Sandbox Code Playgroud)\n

通用分配方法

\n

您可以创建一个带有赋值和检索方法的类(不是 getter/setter 对,因为An accessor cannot have type parameters ts(1094))。

\n

例子:

\n
class MyClass {\n    private _n: number = 42;\n    \n    // infers return type `number`\n    getN() {\n        return this._n;\n    }\n\n    setN<T>(n: NonNegativeInteger<T>) {\n        // Optionally error check:\n        if (Number.isInteger(n) || n <= 0) {\n            throw new Error();\n        }\n        this._n = value;\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

  • 哇伊·佐伊,打字不错!我期待有一天能在新闻中认出你的名字! (6认同)
  • 我不认为“number extends T ? never : ...”是必要的,因为您在声明 &lt;T extends number&gt; 中指定了它。 (2认同)

C S*_*ver 6

不,这是不可能的;uintJavaScript中没有* 或类似字符,因此TypeScript中没有相应的类型。有一个针对Contracts开放功能请求,如果您实施了该请求,那么您将可以提供诸如此类的更可靠的断言。

*此类数据类型存在于“ 类型化数组”规范中,但它们是主要为WebGL设计的扩展,不是核心语言的一部分。