Tìm kiếm trang web

typescript - Có thể giới hạn số trong một phạm vi nhất định không


Kể từ TypeScript 2.0 RC (hoặc thậm chí beta?) đã có thể sử dụng các kiểu literal số, như trong type t=1 | 2;. Có thể giới hạn một kiểu thành một phạm vi số, ví dụ 0-255, mà không cần viết ra 256 số trong kiểu không?

Trong trường hợp của tôi, một thư viện chấp nhận giá trị màu cho bảng màu từ 0-255, và tôi thích chỉ đặt tên một vài màu nhưng giới hạn trong khoảng 0-255:

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

Câu trả lời #1

Chỉnh sửa: đây là một câu trả lời cũ. TS >= 4.5 hiện đã có các công cụ để xử lý vấn đề này, mặc dù có thể có hoặc không bị hạn chế cho trường hợp sử dụng của bạn. Đối với các phạm vi nhỏ, câu trả lời này vẫn hiệu quả:

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

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

type T = IntRange<20, 300>

---- Câu trả lời cũ ----

Không, điều đó không khả thi. Loại ràng buộc chính xác kiểu đó chưa có trong TypeScript (chưa?)

Chỉ kiểm tra/khẳng định khi chạy mới có thể đạt được điều đó :(

Câu trả lời #2

Nếu bạn có phạm vi nhỏ, bạn luôn có thể viết một cái gì đó như:

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

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

Tất nhiên nó chỉ hoạt động với các số nguyên và xấu xí kinh khủng :)

Câu trả lời số 3

Với Typescript 4.5, có thể thực hiện loại bỏ đệ quy đuôi trên các kiểu có điều kiện.

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>

Câu trả lời #4

Vâng, điều đó là có thể NHƯNG:

Giải pháp thứ nhất sẽ là một giải pháp tạm Giải pháp thứ hai sẽ là giải pháp một phần (từ x đến y, trong đó y là một số nhỏ, 43 trong trường hợp của tôi) Giải pháp thứ ba sẽ là giải pháp hoàn chỉnh nhưng thực sự nâng cao với Transformers, Decorators, v.v.

1. Giải pháp bẩn (cách dễ nhất và nhanh chóng trước) sử dụng giải pháp @Adam-Szmyd:

type RangeType = 1 | 2 | 3

nếu bạn cần một phạm vi rộng, chỉ cần in và sao chép/dán:

// 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))

Bạn có thể bị hấp dẫn để làm những việc như thế này

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

nhưng thay vào đó sử dụng các hàm

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

Nhưng hiện tại điều này không hoạt động, chỉ hoạt động nếu là một literal. Thậm chí lỗi cũng không hoàn toàn chính xác.

2. Giải pháp một phần (nguồn)

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. Giải pháp hoàn chỉnh nhưng thực sự nâng cao với Transformers, Decorators, v.v.

Sử dụng các hàm trong giải pháp đầu tiên, bạn có thể thay thế tại compiletime bằng các giá trị, sử dụng transformer. Tương tự, nhưng trên runtime sử dụng decorators.

Câu trả lời #5

Hiện tại chưa thể, nhưng đã có một vấn đề mở trên GitHub. Hiện họ vẫn đang chờ một đề xuất, nhưng chức năng này có thể sẽ xuất hiện vào một ngày nào đó.

Tóm lại, bạn sẽ không thể sử dụng một phạm vi số như một kiểu cho đến khi một đề xuất được công bố.

Hiện tại đã có một đề xuất. Để biết thêm chi tiết, xem Các loại khoảng/Các loại bất đẳng thức.

Câu trả lời số 6

Cập nhật 1

Kể từ Typescript phiên bản 4.5 thêm đánh giá đệ quy đuôi của các kiểu điều kiện. Liên kết vấn đề

Bây giờ số lớn nhất có thể là 998. Nó hoàn toàn đủ cho câu hỏi của bạn.

Liên kết Sân chơi

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

Câu trả lời gốc

Điều này giờ đây có thể với Typescript 4.1 Kiểu Điều kiện Đệ quy

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'.

Nhưng thật không may, nếu chiều dài của bạn quá dài, kiểu đệ quy sẽ thất bại

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

Ừ, nó hoạt động nhưng không hoàn toàn hoạt động. :)

Câu trả lời số 7

Mặc dù không phải là giải pháp tốt nhất (vì một số kiểm tra sẽ được thực hiện tại thời gian chạy), nhưng đáng để nhắc rằng "các kiểu mờ" có thể giúp đảm bảo rằng bạn đang nhập các giá trị mong đợi.

Đây là một ví dụ:

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

Bài viết liên quan