【发布时间】:2014-01-20 01:30:41
【问题描述】:
这个数字是一个非常松散的表示,有时需要收紧。就我而言,我希望一个变量只能采用非负整数。有没有办法在 TypeScript 中强制执行此约束?
【问题讨论】:
-
不,TypeScript 或 JavaScript 中没有类似的东西。您需要以不同的方式强制执行它(例如使用保护值集的函数或属性)。
这个数字是一个非常松散的表示,有时需要收紧。就我而言,我希望一个变量只能采用非负整数。有没有办法在 TypeScript 中强制执行此约束?
【问题讨论】:
是的,模板文字允许这样做;观察:
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 评论:
您将如何在类字段或变量类型上使用它? myField: NonNegativeInteger = 42 似乎不起作用——我不确定在这种情况下作为泛型类型传入什么。您能否提供一个在这种情况下如何使用泛型的示例?
了解在 Typescript 中,文字本身被视为类型;例如:10 可分配给某些let x: number,但只有10 可分配给某些let x: 10。此外,Typescript 有一个强大的类型推断系统,但它只能走这么远才成为开发的负担。上述类型的目标是做以下两件事之一:
您的问题不仅适用于类字段,也不适用于上述类型。 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 对,因为 An accessor cannot have type parameters 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;
}
}
【讨论】:
myField: NonNegativeInteger<number> = 42 似乎不起作用——我不确定在这种情况下作为泛型类型传入什么。您能否提供一个在这种情况下如何使用泛型的示例?
negate(4/2) 或 negate(1+1) 不起作用
不,这是不可能的; JavaScript 中没有* uint 或类似的,所以 TypeScript 中没有对应的类型。有一个 open feature request for Contracts 可以让您提供更强大的断言,如果它曾经实现的话。
* Typed Array specification 中存在此类数据类型,但这些是主要为 WebGL 设计的扩展,不是核心语言的一部分。
【讨论】:
@CSnover 的答案现在已过时为 the feature request has been rejected。
但确实如 CSnover 所说,这在 TypeScript 中是不可能的。
This Babel plugin 可能是您可能拥有的最接近的方法。
【讨论】: