【问题标题】:Is it possible to restrict number to a certain range是否可以将数字限制在一定范围内
【发布时间】:2017-01-22 12:15:22
【问题描述】:

由于 typescript 2.0 RC(甚至是 beta?),可以使用数字文字类型,如 type t = 1 | 2;。是否可以将类型限制为数字范围,例如0-255,类型中不写出256个数字?

在我的例子中,一个库接受从 0-255 的调色板的颜色值,我宁愿只列举几个,但将其限制为 0-255:

const enum paletteColor {
  someColor = 25,
  someOtherColor = 133
}
declare function libraryFunc(color: paletteColor | 0-255); //would need to use 0|1|2|...

【问题讨论】:

  • 注意:枚举定义了一组命名的 numeric 常量,而不是新类型。因此,不需要声明可以传递数字而不是paletteColors。
  • @Burt_Harris 是的。还需要一种将枚举限制为 0-255 的方法。也可以只使用索引器对象而不是枚举,尽管有点难看。无论哪种方式都不需要|,在最好的情况下它应该只是paletteColor,如果它被限制为0-255,或者只是0-255。
  • 注意:从 TS 2.4 开始,字符串文字现在可以作为枚举值 blogs.msdn.microsoft.com/typescript/2017/06/27/…
  • 仅供参考,您所要求的称为“依赖类型”,TS 中不存在此功能。一些具有此功能的语言是 Agda、Idris、Coq。

标签: typescript types


【解决方案1】:

更新 1

从 typescript v4.5 开始添加tail recursive evaluation of conditional typesIssue Link

现在最大数可以是 998。完全可以回答你的问题。

Playground Link

type Ran<T extends number> = number extends T ? number :_Range<T, []>;
type _Range<T extends number, R extends unknown[]> = R['length'] extends T ? R[number] : _Range<T, [R['length'], ...R]>;

type R5 = Ran<998>
const a: R5 = 3 // correct
const b: R5 = 999 // wrong

原始答案

现在可以使用 Typescript 4.1 Recursive Conditional Types

type Range<T extends number> = number extends T ? number :_Range<T, []>;
type _Range<T extends number, R extends unknown[]> = R['length'] extends T ? R['length'] : R['length'] | _Range<T, [T, ...R]>;

type R5 = Range<5>
const a: R5 = 3 // correct
const b: R5 = 8 // error. TS2322: Type '8' is not assignable to type '0 | 1 | 2 | 3 | 4 | 5'.

但是很遗憾,如果你的长度太长,递归类型会失败

type R23 = Range<23>
// TS2589: Type instantiation is excessively deep and possibly infinite.

嗯,它有效,但不是真的有效。 :)

【讨论】:

【解决方案2】:

Typescript 4.5 可以执行tail-recursion elimination on conditional types

type Enumerate<N extends number, Acc extends number[] = []> = Acc['length'] extends N
  ? Acc[number]
  : Enumerate<N, [...Acc, Acc['length']]>

type Range<F extends number, T extends number> = Exclude<Enumerate<T>, Enumerate<F>>

type T = Range<20, 100>

【讨论】:

  • 确实,尾递归消除允许比其他解决方案的 41 更多的递归调用,尽管没有完全消除它(例如 Rangee&lt;10, 1000&gt; 仍然失败)。 PS:type ArrayToUnion&lt;T extends unknown[]&gt; = T extends (infer U)[] ? U : never; 不一样,但更简单吗?
  • 其实我们可以做更简单的type TupleToUnion&lt;T extends unknown[]&gt; = T[number]。见Tuple to Union
【解决方案3】:

目前不可能,但有一个 open issue on GitHub。 目前他们仍在等待提案,但这一功能可能会在某一天到来。

简而言之,在提案出来之前,您将无法使用一系列数字作为类型。


更新 - 2021 年 8 月

现在有一个提案。详情请见Interval Types / Inequality Types

【讨论】:

【解决方案4】:

这对我有用,可以限制 html 文本区域的高度。它将测试值剪辑到 5...20 范围内。

const rows = Math.min(Math.max(stringArray.length, 5), 20);

【讨论】:

    【解决方案5】:

    虽然不是最佳解决方案(因为一些检查将在运行时处理),但值得一提的是 "opaque types" 可以帮助强制您输入预期值。

    这是一个例子:

    type RGBColor = number & {_type_: "RGBColor"};
    
    const rgb = (value: number): RGBColor => {
      if (value < 0 || value > 255) {
        throw new Error(`The value ${value} is not a valid color`);
      }
    
      return value as RGBColor;
    };
    
    // Compiler errors
    const color1: RGBColor = 200; // fail - number is not RGBColor
    const color2: RGBColor = 300; // fail - number is not RGBColor
    
    // Runtime error
    const color3: RGBColor = rgb(300); // fail - The value 300 is not a valid color
    
    // Pass
    const color4: RGBColor = rgb(100);
    const color5: RGBColor = rgb(255);
    

    【讨论】:

      【解决方案6】:

      编辑:啊,我没有仔细阅读提供的答案! @titusfx 已经以另一种形式提供了这个答案。 与他的方法一样,这在您可以生成的数字数量方面受到限制。这不是一个实际的解决方案,而是一种在非常有限的数字范围内有效的解决方法!

      原答案:

      有一个解决方法。借用答案https://stackoverflow.com/a/52490977(将这个解决方案限制为 TypeScript v 4.1 及更高版本):

      type _NumbersFrom0ToN<
          Nr extends number
          > =
          Nr extends Nr ?
              number extends Nr ?
                  number :
                  Nr extends 0 ?
                      never :
                      _NumbersFrom0ToNRec<Nr, [], 0> :
              never;
      
      type _NumbersFrom0ToNRec<
          Nr extends number,
          Counter extends any[],
          Accumulator extends number
          > =
          Counter['length'] extends Nr ?
              Accumulator :
              _NumbersFrom0ToNRec<Nr, [any, ...Counter], Accumulator | Counter['length']>;
      
      type NrRange<
          Start extends number,
          End extends number
          > =
          Exclude<_NumbersFrom0ToN<End>, _NumbersFrom0ToN<Start>>;
      
      let nrRange: NrRange<14, 20>;
      

      创建类型14 | 15 | 16 | 17 | 18 | 19。为了完成这项工作,我们只需要利用 TypeScript 可以通过新改进的元组类型检查的长度属性计数的特性。所以我们只要扩展一个数组,只要数组的长度和输入的数不一样。当我们扩展数组时,我们会记住我们已经访问过的长度。这反过来会产生一个带有额外步骤的计数器。

      编辑:我将这些类型放在一个包中以便于重用:https://www.npmjs.com/package/ts-number-range

      【讨论】:

      • 这很有趣,但遗憾的是,它的工作方式对于编译器来说极其复杂。例如。 NrRange&lt;0, 42&gt; 已经给出“类型实例化过深并且可能无限。(2589)”,或多或少明显的原因。它对于范围很长的边缘情况很有用,手动输入它没有用,但仍然“对于这种方法来说大小合理”。
      • @ASDFGerte 好点,我没有用更大的数字测试它。但是你是对的,需要一种可以更优雅地处理这个问题的集成类型:) 但是除了舒适之外还有另一种情况。当您不知道界限并且用户提供长度时。然后你仍然可以将他限制在那个范围内,否则它就会崩溃:D。无论如何,这并不理想,但总比没有好。
      • @ASDFGerte 我在包装自述文件中添加了免责声明。谢谢你的提示!
      【解决方案7】:

      我不相信还有一种“简单”的方法可以做到这一点。我在这里阅读了很多 cmets:https://github.com/microsoft/TypeScript/issues/15480 一些非常好的想法,但需要考虑很多事情。

      【讨论】:

        【解决方案8】:

        是的,有可能但是

        第一个。解决方案将是一个肮脏的解决方案 第 2 个。解决方案将是部分的(从 x 到 y,其中 y 是一个小数字,在我的情况下为 43) 第 3 个。解决方案将是一个完整的解决方案,但在变形金刚、装饰器等方面确实取得了进步。

        1.使用@Adam-Szmyd 解决方案的肮脏解决方案(首先是最简单快速的方法):

        type RangeType = 1 | 2 | 3
        

        如果您需要广泛的范围,只需打印和复制/粘贴:

        // Easiest just incremental
        let range = (max) => Array.from(Array(max).keys()).join(" | ");
        console.log('Incremental')
        console.log(range(20))
        // With range and steps
        let rangeS = (( min, max, step) => Array.from( new Array( max > min ? Math.ceil((max - min)/step) : Math.ceil((min - max)/step) ), ( x, i ) => max > min ? i*step + min : min - i*step ).join(" | "));
        console.log('With range and steps')
        console.log(rangeS(3,10,2))

        你可能会做这样的事情

        const data = [1, 2, 4, 5, 6, 7] as const;
        type P = typeof data[number];
        

        而是使用函数

        const rangeType20 = Array.from(Array(20).keys()) as const;
        

        但目前这不起作用,只有当是文字时才有效。甚至错误也不是很正确。

        2。部分解决方案 (source)

        type PrependNextNum<A extends Array<unknown>> = A['length'] extends infer T ? ((t: T, ...a: A) => void) extends ((...x: infer X) => void) ? X : never : never;
        
        type EnumerateInternal<A extends Array<unknown>, N extends number> = { 0: A, 1: EnumerateInternal<PrependNextNum<A>, N> }[N extends A['length'] ? 0 : 1];
        
        export type Enumerate<N extends number> = EnumerateInternal<[], N> extends (infer E)[] ? E : never;
        
        export type Range<FROM extends number, TO extends number> = Exclude<Enumerate<TO>, Enumerate<FROM>>;
        
        type E1 = Enumerate<43>;
        
        type E2 = Enumerate<10>;
        
        type R1 = Range<0, 5>;
        
        type R2 = Range<0, 43>;
        

        3.完整的解决方案,但使用TransformersDecorators 等确实取得了进步。

        使用第一个解决方案中的函数,您可以使用转换器将 compiletime 替换为值。同样,但在 runtime 上使用装饰器。

        【讨论】:

        • 值得注意的是,到目前为止,任何递归解决方案都仅限于固定的缩减深度,并且一旦达到它就会抛出Type instantiation is excessively deep and possibly infinite.(2589)(只是向其他读者提及为什么“y在哪里一个小数字”约束)
        • 你能实现第三种解决方案吗?
        【解决方案9】:

        是否可以将类型限制为数字范围,例如0-255,类型中不写出256个数字?

        直到现在还不可能,但你可以做一个生活小窍门,用一行代码生成所需的序列并复制/粘贴结果

        new Array(256).fill(0).map((_, i) =&gt; i).join(" | ")

        【讨论】:

          【解决方案10】:

          不使用静态类型检查,仅在运行时,例如使用像 io-ts 这样的库 例如,您可以在其中使用taggedUnionhttps://github.com/gcanti/io-ts/issues/313

          【讨论】:

            【解决方案11】:

            如果你的范围很小,你总是可以这样写:

            type MyRange = 5|6|7|8|9|10
            
            let myVar:MyRange = 4; // oops, error :)
            

            当然它只适用于整数,而且丑得要命:)

            【讨论】:

            • "没有在类型中写出 256 个数字"
            • @Quentin2 不要写,在控制台中生成一个字符串,然后复制/粘贴到您的代码中...new Array(256).fill(0).map((_, i) =&gt; i).join(" | ")
            • 有什么方法可以限制只有正数,比如 1,2,1005 等。
            • 虽然对于少数人来说不是很好,但对于我的 0 - 6 的场景来说效果很好。谢谢!
            【解决方案12】:

            不,这是不可能的。那种精确的类型约束在 typescript 中不可用(还没有?)

            只有运行时检查/断言才能实现:(

            【讨论】:

            猜你喜欢
            • 2015-04-06
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-10-04
            • 2020-11-25
            • 1970-01-01
            • 2011-08-25
            相关资源
            最近更新 更多