是否可以将数量限制在一定范围内

时间:2016-09-14 15:45:45

标签: typescript types

由于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|...

11 个答案:

答案 0 :(得分:28)

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

type MyRange = 5|6|7|8|9|10

let myVar:MyRange = 4; // oops, error :)

当然它只适用于整数而且很丑陋地说:)

答案 1 :(得分:10)

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

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

答案 2 :(得分:6)

暂时不可能,但是有一个open issue on GitHub。 目前,他们仍在等待提案,但是此功能可能有一天会出现。

简而言之,除非提出建议,否则您将无法使用一系列数字作为类型。

答案 3 :(得分:6)

是的,有可能

第一。解决方案将是肮脏的解决方案 第二。解决方案将是局部的(从x到y,其中y是一个小数,在我的情况下为43) 第三。该解决方案将是一个完整的解决方案,但在变形金刚,装饰器等方面确实会进步。

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];

showing that P has the enumerated types

但是使用函数

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

showing that can't be done with functions

但是目前这不起作用,只有在字面意义上才起作用。甚至错误也不是很正确。

2。部分解决方案(source Partial solution

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上使用修饰符。

答案 4 :(得分:4)

虽然不是最佳解决方案(因为一些检查将在运行时处理),但值得一提的是 "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);

答案 5 :(得分:3)

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

到现在为止不可能,但是您可以进行一次生活破解,并用一行代码和复制/粘贴结果生成所需的序列

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

答案 6 :(得分:2)

编辑:啊,我没有仔细阅读提供的答案! @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>;

range type

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

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

答案 7 :(得分:1)

不使用静态类型检查,仅在运行时使用例如io-ts之类的库 例如,您可以在其中使用taggedUnion的位置: https://github.com/gcanti/io-ts/issues/313

答案 8 :(得分:0)

我建议使用类似这样的东西:

class Cinema {

private openingHour: number;

constructor(openingHour: 5 | 6 | 7) {

    this.openingHour = openingHour;

}

}

let yesPlanet = new Cinema(9);

当您使用值“ 9”初始化yesPlanet时,会收到错误消息,因为它超出范围。 希望有帮助。

答案 9 :(得分:0)

我不认为仍然存在“简便”的方法。我在这里阅读了很多评论:https://github.com/microsoft/TypeScript/issues/15480一些非常好的主意,但有很多事情要考虑。

答案 10 :(得分:0)

现在可以使用 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.

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