Typecript dẫn xuất kiểu liên hiệp từ các giá trị tuple / mảng


95

Giả sử tôi có danh sách const list = ['a', 'b', 'c']

Có thể bắt nguồn từ kiểu liên minh giá trị này 'a' | 'b' | 'c'không?

Tôi muốn điều này vì tôi muốn xác định kiểu chỉ cho phép các giá trị từ mảng tĩnh và cũng cần liệt kê các giá trị này trong thời gian chạy, vì vậy tôi sử dụng mảng.

Ví dụ về cách nó có thể được triển khai với một đối tượng được lập chỉ mục:

const indexed = {a: null, b: null, c: null}
const list = Object.keys(index)
type NeededUnionType = keyof typeof indexed

Tôi tự hỏi liệu có thể làm điều đó mà không sử dụng một bản đồ được lập chỉ mục không.


Vì vậy, về cơ bản, bạn muốn tạo các loại một cách nhanh chóng?
SwiftsNamesake

Các kiểu chỉ tồn tại khi biên dịch, vì vậy bạn không thể tạo động chúng trong thời gian chạy.
jonrsharpe

Đây là một câu hỏi thú vị. Trường hợp sử dụng của bạn là gì?
unional

Câu trả lời:


200

CẬP NHẬT Tháng 2 năm 2019

Trong TypeScript 3.4, sẽ được phát hành vào tháng 3 năm 2019 , có thể yêu cầu trình biên dịch suy ra loại một bộ ký tự là một bộ nhiều ký tự , thay vì như, giả sử string[], bằng cách sử dụng as constcú pháp . Loại khẳng định này khiến trình biên dịch suy ra loại hẹp nhất có thể cho một giá trị, bao gồm cả việc tạo ra mọi thứ readonly. Nó sẽ giống như thế này:

const list = ['a', 'b', 'c'] as const; // TS3.4 syntax
type NeededUnionType = typeof list[number]; // 'a'|'b'|'c';

Điều này sẽ loại bỏ nhu cầu về bất kỳ loại chức năng trợ giúp nào. Chúc may mắn một lần nữa cho tất cả!


CẬP NHẬT tháng 7 năm 2018

Có vẻ như, bắt đầu với TypeScript 3.0, TypeScript có thể tự động suy ra các loại tuple . Sau khi được phát hành, tuple()hàm bạn cần có thể được viết ngắn gọn là:

export type Lit = string | number | boolean | undefined | null | void | {};
export const tuple = <T extends Lit[]>(...args: T) => args;

Và sau đó bạn có thể sử dụng nó như thế này:

const list = tuple('a','b','c');  // type is ['a','b','c']
type NeededUnionType = typeof list[number]; // 'a'|'b'|'c'

Hy vọng rằng hiệu quả cho mọi người!


CẬP NHẬT Tháng 12 năm 2017

Vì tôi đã đăng câu trả lời này, tôi đã tìm ra cách để suy ra các loại tuple nếu bạn muốn thêm một hàm vào thư viện của mình. Kiểm tra chức năng tuple()trong tuple.ts . Sử dụng nó, bạn có thể viết những điều sau và không lặp lại chính mình:

const list = tuple('a','b','c');  // type is ['a','b','c']
type NeededUnionType = typeof list[number]; // 'a'|'b'|'c'

Chúc may mắn!


XUẤT XỨ tháng 7 năm 2017

Một vấn đề là nghĩa đen ['a','b','c']sẽ được suy ra là kiểu string[], vì vậy hệ thống kiểu sẽ quên mất các giá trị cụ thể. Bạn có thể buộc hệ thống kiểu nhớ từng giá trị dưới dạng một chuỗi ký tự:

const list = ['a' as 'a','b' as 'b','c' as 'c']; // infers as ('a'|'b'|'c')[]

Hoặc, có thể tốt hơn, giải thích danh sách dưới dạng một loại tuple:

const list: ['a','b','c'] = ['a','b','c']; // tuple

Đây là sự lặp lại gây khó chịu, nhưng ít nhất nó không giới thiệu một đối tượng không liên quan trong thời gian chạy.

Bây giờ bạn có thể nhận được liên minh của mình như thế này:

type NeededUnionType = typeof list[number];  // 'a'|'b'|'c'.

Hy vọng rằng sẽ giúp.


Đây là một giải pháp tuyệt vời cho một vấn đề mà tôi gặp phải khá thường xuyên, khi tôi cần kiểm tra thời gian chạy (sử dụng bộ tuple) và kiểm tra thời gian biên dịch bằng cách sử dụng một liên hiệp kiểu. Bất cứ ai biết nếu có những nỗ lực để thêm hỗ trợ cho việc nhập tuple ngầm vào ngôn ngữ?
Jørgen Tvedt

Cố gắng giải pháp của bạn, nhưng nó không làm việc vớicont xs = ['a','b','c']; const list = tuple(...xs);
Miguel Carvajal

1
Nó không được phép làm việc với điều đó, vì do thời gian bạn làm const xs = ['a','b','c']trình biên dịch đã mở rộng xstới string[]và hoàn toàn quên về các giá trị cụ thể. Tôi không thể giúp đỡ hành vi đó ít nhất là kể từ TS3.2 ( as constmặc dù có thể có ký hiệu trong tương lai hoạt động). Dù sao thì tôi nghĩ rằng câu trả lời là giá đỡ vẫn đúng như tôi có thể làm được (tôi đề cập trong đó ['a','b','c']được suy ra là string[]) vì vậy tôi không chắc bạn cần thêm gì nữa.
jcalz

3
ai đó có thể giải thích những gì [number]hiện trong list[number]?
Orelus

3
Nó được phân tích cú pháp là (typeof list)[number]... không phải typeof (list[number]). Kiểu T[K]này là kiểu tra cứu lấy kiểu thuộc tính của TK. Trong đó (typeof list)[number], bạn đang nhận được các loại thuộc tính của (typeof list)khóa của chúng number. Mảng như typeof listchữ ký chỉ mục số , vì vậy numberkhóa của chúng tạo ra sự kết hợp của tất cả các thuộc tính được lập chỉ mục số.
jcalz

15

Cập nhật cho TypeScript 3.4:

Một cú pháp mới được gọi là "const context" sẽ xuất hiện trong TypeScript 3.4 sẽ cho phép một giải pháp đơn giản hơn mà không yêu cầu một cuộc gọi hàm như đã trình bày. Tính năng này hiện đang được xem xét như đã thấy trong bài PR này .

Tóm lại, cú pháp này cho phép tạo ra các mảng không thay đổi có kiểu hẹp (tức là kiểu ['a', 'b', 'c']thay vì ('a' | 'b' | 'c')[]hoặc string[]). Bằng cách này, chúng ta có thể tạo các kiểu liên hợp từ các chữ dễ dàng như được hiển thị bên dưới:

const MY_VALUES = <const> ['a', 'b', 'c']
type MyType = typeof MY_VALUES[number]

Trong cú pháp thay thế:

const MY_VALUES = ['a', 'b', 'c'] as const
type MyType = typeof MY_VALUES[number]

Xin chào, bạn vừa tìm thấy câu trả lời sau khi đăng câu hỏi này. Tôi sẽ để bạn kiếm được một số nghiệp chướng nếu bạn muốn trả lời;) stackoverflow.com/questions/56113411/…
Sebastien Lorber

2

Không thể làm điều này với Array.

Lý do là, ngay cả khi bạn khai báo biến là const, nội dung của một mảng vẫn có thể thay đổi, do đó @jonrsharpe đề cập đây là thời gian chạy.

Với những gì bạn muốn, có thể tốt hơn nếu sử dụng interfacevới keyof:

interface X {
    a: string,
    b: string
}

type Y = keyof X  // Y: 'a' | 'b'

Hoặc enum:

enum X { a, b, c }

Có cách nào để loại trừ các trường nhất định không? Ví dụ: nếu tôi chỉ muốn atrường ..
neomib

Bạn có thể sử dụng Pick<T, U>cho điều đó.
unional
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.