没有子类型number
在 TypeScript 中,它与以下函数返回的值完全对应true
:
function validMode(mode: number): boolean {
return mode > 0 && mode <= 5;
}
console.log(validMode(1), validMode(5),
validMode(2.5), validMode(Math.PI)); // true, true, true, true
console.log(validMode(0), validMode(6),
validMode(-1), validMode(Infinity)); // false, false, false, false
请注意,像这样的数字2.5
and Math.PI
被接受,因为 JavaScript 中的值typeof mode === "number"
是双精度浮点数并且不限于整数。
为了支持这种类型,TypeScript 可能需要以下一个或两个缺失的功能:
-
细化类型 https://en.wikipedia.org/wiki/Refinement_type,您可以在其中以谓词的形式对类型设置条件;有一个关于此功能的请求微软/TypeScript#7599 https://github.com/microsoft/TypeScript/issues/7599,但看起来那里没有任何工作正在进行。
-
数值的数学运算文字类型 https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types,您实际上可以在其中编写谓词,例如ValidMode > 0
编译器会理解这对应于true
or false
取决于文字类型ValidMode
。有一个关于此功能的请求微软/TypeScript#26382 https://github.com/microsoft/TypeScript/issues/26382,但同样,我没有看到任何明显的工作来支持这一点。
所以一般来说是不可能的。
当然是原版mode
您编写的类型不接受之间的任意数字1
and 5
,但仅限整数值。更像这样的东西:
function validMode(mode: number): boolean {
return Number.isInteger(mode) && mode > 0 && mode <= 5;
}
console.log(validMode(1), validMode(5)); // true, true
console.log(validMode(2.5), validMode(Math.PI)); // false, false
而且,如你所知,这样的类型can被表示,但仅作为明确的union https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#union-types有限数量的数字文字:
type ValidMode = 1 | 2 | 3 | 4 | 5;
实际上,如果一个工会的成员人数不超过 25 人,那么它就会表现得合理;如果它有超过 25 个但少于 10,000 个成员,则在某些情况下它会表现得很奇怪(请参阅微软/TypeScript#40803 https://github.com/microsoft/TypeScript/issues/40803例如),如果它有超过 10,000 名成员,您将接近或通过一些关于工会规模的硬性限制(请参阅这个错误信息 https://github.com/microsoft/TypeScript/blob/e638af7560530a76439d0ce9e2c517090fe6a60a/src/compiler/diagnosticMessages.json#L2513).
TypeScript 缺少“范围类型”的概念,人们可以在其中轻松编写
type ValidMode = Range<1, 5>; // or would that be Range<1, 6>? Inclusive/exclusive ????♂️
有一个开放的功能请求,位于微软/TypeScript#15480 https://github.com/microsoft/TypeScript/issues/15480。有一些涉及递归或泛型的解决方法,甚至模板文字类型 https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-1.html#template-literal-types,但没有一个既可以理解又表现良好。如果您查看链接的问题,您可以找到一些导致联合的递归实现,如果您愿意,这将不起作用Range<0, 1.0e+6>
甚至Range<0, 50>
。除了娱乐目的之外,我不建议使用模板文字类型来解析字符串。
所以目前来看,这也是不可能的。
完全不同的方法是接受编译器无法轻松且准确地表示您的类型。相反,我们想创建一个branded https://github.com/microsoft/TypeScript/wiki/FAQ#can-i-make-a-type-alias-nominal的亚型number
called ValidMode
。我们可以写一个类型保护函数 https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates对其执行运行时检查number
输入,并确定该输入是否是ValidMode
。如果是这样,我们会使用虚拟属性“标记”该号码,将其标记为有效。如果没有,我们就不管它。然后你可以编写只接受的函数ValidMode
并且编译器将强制执行此操作:
type ValidMode = number & { __validMode: true };
function validMode(mode: number): mode is ValidMode {
return Number.isInteger(mode) && mode > 0 && mode <= 5;
}
function onlyAcceptValidModes(mode: ValidMode) {
console.log(mode.toFixed(2));
}
这使得使用起来有点困难ValidMode
,因为编译器无法验证像这样的数字文字4
匹配它:
onlyAcceptValidModes(4); // error, number is not assignable to ValidMode
相反,您需要执行运行时验证测试以使您的值获得批准:
const someNumber = Math.random() < 0.5 ? 4 : 8;
onlyAcceptValidModes(someNumber); // error again
if (validMode(someNumber)) {
onlyAcceptValidModes(someNumber) // okay
}
通过这种方式,您可以在 TypeScript 中模拟细化类型。由于测试仅在运行时存在,因此它可以像您想要的那样复杂。
Playground 代码链接 https://www.typescriptlang.org/play?ts=4.2.3#code/HYQwtgpgzgDiDGEAEApeIA2AvJBvAUPkkqJLAsgPLDIFHFIBmArsPAC4CWA9sEgG6ZOAEwCy3YRAAUYCRABcJZmABGEAE4BKRSu7cMEEHwIMG6iO2bq+syUgB8SAAxIAZK6S3kAHgC8SAFYAbnpiAF9QpHheKH0IADoMbgBzKUEMEXFJKQBGTQAaASExOSkAgqKMkuyAJnjywvTM0tEQdgALeIAFAElNTSCkAHohpHZ1ZghC8cnpiamx+cjo4FiDRJS04qzpJwqm6ukANn3t0oBaPMaz7J7gRk5gTnYAT37BkaZMKAXGb9--oU-hgfoRwmCSOBoHBEEgACoAd24eEiLDYXF4lWa2S8imAyjUWh0egMRhRpmI5ks1iQADkCRp4pwoHd2BBkhoZHJNG4PF4HM5eZ45Eg-IEQqYIqYVmsEklUgcdrlTlUleUBsNRjMFtrljE4hsFTdpHUGljDlJWh1un0NZ9gT8gf9Iq8YMgAGrGpD+HJIAA+SBq-qQAGZgwAWYPBQgQiIQ0jQijw9rmWgul5upCe1Ui-z41QaNx4JAAfRLirkim1SDCEoYaI4PD4FZxlaUBaJwrszKzXpMFKpVj49I7TJZwDZHPUXMkPPcXeQjhc8-5YujktRrEbmN4GBeAEF4IgYOxs9joDOFL2c7PyRSZQb5Zf4uxuAAxTgADwgwikNXekRSgwu4HkeEAnmehxQFI4Z2qMGjqNw6iFPmhJID2wDcOwSAgFAUCcMkoAqAYYzIpBOwQsQMrYbEkAjmh-hWp06hGMI3BgFIPLeM49RIAA-EgkaKAAHHWxAgYex6nsa0G0RA9EaHBSAIUhOHJCAjyUehjBIFsN7SHJClaDy-amBJYEQTJUiGQyWiakg3AANYgC8gGxvgYRAA