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

标签 typescript type-safety

数字是一个非常宽松的表示,有时需要收紧。就我而言,我希望变量只能采用非负整数。有没有办法在 TypeScript 中强制执行此约束?

最佳答案

2021 年更新:

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

type NonNegativeInteger<T extends number> =
    number extends T 
        ? never 
        : `${T}` extends `-${string}` | `${string}.${string}`
            ? never 
            : T;

请注意,number extends T 是限制一般number 类型所必需的。

用法:

function negate<N extends number>(n: NonNegativeInteger<N>): number {
    return -n;
}


negate(3); // success
negate(3.1); // failure
negate(-3); // failure
negate(-3.1); // failure

用法

回应@ianstarz 评论:

How would you use this on a class field or variable type? myField: NonNegativeInteger = 42 doesn't seem to work—I'm not sure what to pass in as the generic type in this case. Can you also provide an example of how to use the generic in this case?

了解在 Typescript 中,文字本身被视为类型;示例:10 可分配给某些 let x: number,但只有 10 可分配给某些 let x: 10。此外,Typescript 有一个强大的类型推断系统,但它在成为开发负担之前只能走这么远。上述类型的目标是做两件事之一:

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

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

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

type Identity<T> = Identity;

// Generic type 'Example' requires 1 type argument(s)
let x: Identity = 10;

相比于:

type Identity<T> = Identity;

function identity<T>(x: Identity<T>): T {
    return x;
}

let y = identity(10); // Success, y has type `number`
const z = identity(10); // Success, z has type `10`

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

有限文字联合

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

type EvenDigit = 0 | 2 | 4 | 6 | 8;

let x: EvenDigit = 2; // Success
let y: EvenDigit = 10; // Failure

如果你疯了,写一个生成联合类型的脚本。请注意,联合类型的成员数量可能有一个特定于版本的上限。

计算的文字联盟

如果你想使用 SUPER meta 这样的东西会生成一系列类型:

// Assumes, for simplicity, that arguments Start and End are integers, and
// 0 < Start < End.
// Examples:
// Range<0, 5> -> 0 | 1 | 2 | 3 | 4 | 5
// Only can calculate so much:
// Range<0, 100> -> 'Type instantiation is excessively deep and possibly infinite.ts(2589)'
// Tail end recursion being introduced in Typescript 4.5 may improve this.
type Range<Start extends number, End extends number> = RangeImpl<Start, End>;
type RangeImpl<
    Start extends number,
    End extends number,
    T extends void[] = Tuple<void, Start>
> = End extends T["length"]
    ? End
    : T["length"] | RangeImpl<Start, End, [void, ...T]>;

// Helper type for creating `N` length tuples. Assumes `N` is an integer
// greater than `0`. Example:
// Tuple<number, 2 | 4> -> [number, number] | [number, number, number, number]
type Tuple<T, N extends number> = TupleImpl<T, N>;
// prettier-ignore
type TupleImpl<T, N extends number, U extends T[] = []> =
    N extends U["length"]
        ? U
        : TupleImpl<T, N, [T, ...U]>;

通用分配方法

您可以创建一个带有赋值和检索器方法的类(不是 getter/setter 对,因为访问器不能有类型参数 ts(1094))。

例子:

class MyClass {
    private _n: number = 42;
    
    // infers return type `number`
    getN() {
        return this._n;
    }

    setN<T>(n: NonNegativeInteger<T>) {
        // Optionally error check:
        if (Number.isInteger(n) || n <= 0) {
            throw new Error();
        }
        this._n = value;
    }
}

关于typescript - 有没有办法在 TypeScript 中表示非负整数,以便编译器可以防止使用分数和负数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21224922/

相关文章:

typescript :与先前参数的已解析类型相同的通用类型

Angular6:自定义组件上的输入绑定(bind)未正确更新

casting - F# 转换一个 int

c# - 如何以通用类型安全的方式验证层次结构中的类?

Java:类型安全 - 未经检查的强制转换

Javascript/Typescript 从函数内部调用外部函数

reactjs - 将 props 传递给 TS 中的 makeStyle

c++ - 重构期间结构的聚合初始化是否安全?

vb.net - VB.NET 中的泛型

typescript - "Type ' () => string[] ' is missing the following properties from type ' string[] ': pop, push, concat, join, and 25 more" typescript 中出现错误