Menu

Tuple TypeScript: cú pháp, phần tử có tên, tùy chọn và rest

Tuple trong TypeScript là một mảng có số phần tử cố định, kiểu của mỗi phần tử được biết theo vị trí, như [string, number]. Tìm hiểu cú pháp, phần tử có tên, phần tử tùy chọn và rest, tuple readonly và as const, trả về tuple từ hàm, và tuple khác mảng ra sao.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

Tuple trong TypeScript là một mảng có số phần tử cố định, trong đó mỗi vị trí có kiểu riêng. [string, number] nghĩa là đúng hai phần tử: đầu tiên là một chuỗi, sau đó là một số. Bạn viết các kiểu trong cặp ngoặc vuông theo đúng thứ tự xuất hiện của các giá trị.

Lúc chạy, tuple chỉ là một mảng JavaScript thông thường. Mọi thứ tuple bổ sung (độ dài cố định và kiểu tại mỗi vị trí) đều được trình biên dịch kiểm tra rồi xóa đi.

Cú pháp tuple

Kiểu tupleChấp nhậnKiểu của length
[string, number]đúng một chuỗi, rồi một số2
[x: number, y: number]như trên, có nhãn cho dễ đọc2
[number, number, number?]2 hoặc 3 số2 | 3
[string, ...number[]]một chuỗi, rồi bao nhiêu số cũng đượcnumber
[...string[], number]bao nhiêu chuỗi cũng được, rồi một sốnumber
readonly [number, number]một cặp không thể thay đổi2
[]chỉ một mảng rỗng0

Mỗi dạng được giải thích ở bên dưới. Kiểu của length đáng để ý: với tuple cố định, nó là một literal type, nên trình biên dịch biết pair.length chính xác là 2.

Trình biên dịch kiểm tra những gì

Một kiểu tuple cố định số phần tử, thứ tự của chúng và kiểu tại mỗi vị trí. Sai bất kỳ điều nào cũng là lỗi biên dịch:

index.ts(2,7): error TS2322: Type '[string]' is not assignable to type '[string, number]'.
  Source has 1 element(s) but target requires 2.
index.ts(3,36): error TS2322: Type 'number' is not assignable to type 'string'.
index.ts(3,40): error TS2322: Type 'string' is not assignable to type 'number'.
index.ts(5,16): error TS2493: Tuple type '[string, number]' of length '2' has no element at index '2'.

Một mảng thông thường không bao giờ bắt được lỗi cuối cùng: với string[], arr[2] đơn giản là một string mà lúc chạy lại tình cờ là undefined.

Phần tử tuple có tên

Nhãn cho biết ý nghĩa của từng vị trí. Chúng không thay đổi gì về kiểu hay cách truy cập theo chỉ số, nhưng trình soạn thảo hiển thị chúng khi rê chuột và trong gợi ý chữ ký hàm, giúp [number, number] bớt khó hiểu hơn nhiều.

Từ TypeScript 5.2, bạn có thể đặt nhãn cho một số vị trí và để trống những vị trí khác, như [first: string, number]. Nhãn chỉ dành cho người đọc: [x: number, y: number] và [number, number] là cùng một kiểu và gán được cho nhau.

Phần tử tùy chọn

Dấu ? sau kiểu của một phần tử biến vị trí đó thành tùy chọn. Các phần tử tùy chọn phải đứng sau các phần tử bắt buộc, và mỗi phần tử tùy chọn mở rộng kiểu của length.

Đọc một phần tử tùy chọn cho ra T | undefined, nên cần một giá trị mặc định trong mẫu destructuring (a = 1) hoặc một phép kiểm tra trước khi tính toán.

Phần tử rest

Phần tử rest, ...T[], đại diện cho bao nhiêu phần tử kiểu T cũng được. Nó có thể nằm ở cuối, ở đầu hoặc ở giữa, và mỗi tuple có tối đa một phần tử rest.

length của một tuple có phần tử rest là number, vì kích thước không còn cố định. Điều vẫn cố định là vị trí của các phần tử có kiểu riêng.

Tuple readonly và as const

readonly [T, U] loại bỏ push, pop, splice và phép gán theo chỉ số, đúng như một giá trị có độ dài cố định nên có. Viết as const sau một mảng literal sẽ suy ra một tuple readonly gồm các literal type.

(typeof SIZES)[number] biến tuple thành union của các kiểu phần tử, một mẫu được trình bày trong trang indexed access type. Không thể truyền một tuple readonly cho tham số có kiểu là tuple có thể thay đổi, nên những hàm chỉ đọc nên nhận readonly [number, number].

Kiểm tra readonly chỉ có lúc biên dịch. Lúc chạy, mảng không bị đóng băng (phép gán ở trên thật sự đã chạy, như đầu ra cho thấy), nên hãy dùng Object.freeze nếu bạn cần đảm bảo lúc chạy.

Trả về tuple từ hàm

Trả về nhiều giá trị dưới dạng tuple chính là cách useState của React hoạt động (const [value, setValue] = useState(0)). Điểm cần lưu ý: một mảng literal trong return được suy ra là mảng, không phải tuple.

index.ts(9,13): error TS2365: Operator '+' cannot be applied to types 'number | (() => number)' and 'number'.
index.ts(10,1): error TS2349: This expression is not callable.
  Not all constituents of type 'number | (() => number)' are callable.
    Type 'number' has no call signatures.

Hàm trả về (number | (() => number))[], nên cả hai tên được destructuring đều nhận kiểu union. Có hai cách sửa: chú thích kiểu trả về, hoặc thêm as const.

Giá trị trả về là tuple cho phép nơi gọi tự đặt tên các phần theo ý muốn. Khi có nhiều hơn hai hoặc ba giá trị, hoặc thứ tự không hiển nhiên, hãy trả về một đối tượng: { count, increment } tự nói lên ý nghĩa của nó.

Tuple làm tham số hàm

Một tham số rest có kiểu tuple mô tả toàn bộ danh sách đối số, kể cả đối số tùy chọn. Đây là cách utility type dựng sẵn Parameters<T> biểu diễn các tham số của một hàm.

Spread một tuple vào lời gọi hàm sẽ kiểm tra kiểu từng đối số theo vị trí, điều mà spread một (string | number)[] không làm được.

Tuple và mảng

Mảng (string | number)[]Tuple [string, number]
Độ dàibất kỳcố định (hoặc bị giới hạn bởi phần tử tùy chọn và rest)
Kiểu của x[0]string | numberstring
Kiểu của x[5]string | numberlỗi biên dịch TS2493
Kiểu của lengthnumber2
Thứ tự các kiểukhông theo dõicó theo dõi
Giá trị lúc chạymảng JavaScriptvẫn là mảng JavaScript đó
Cách dùng điển hìnhdanh sách các mục giống nhaunhóm nhỏ cố định: cặp giá trị, tọa độ, [key, value], nhiều giá trị trả về

Tuple cũng xuất hiện trong các kiểu dựng sẵn. Object.entries(obj) trả về [string, T][], và một Map được tạo từ các tuple [key, value]:

Một cái bẫy: tuple có thể thay đổi vẫn có mọi phương thức của mảng, nên pair.push(3) vẫn biên dịch được trên một [string, number] và âm thầm tạo ra một mảng ba phần tử trong khi kiểu nói là hai. Khai báo tuple là readonly sẽ bịt lỗ hổng đó. Và vì kiểu bị xóa khi biên dịch, dữ liệu từ bên ngoài chương trình (JSON, một API) không được kiểm tra theo kiểu tuple lúc chạy: hãy xác thực độ dài và kiểu phần tử trước khi tin dùng.

Variadic tuple type

Kiểu tuple có thể spread các kiểu tuple khác, [...T, ...U]. Kết hợp với generic, điều này giúp khai báo kiểu cho những hàm nối hoặc chèn vào đầu mà vẫn giữ nguyên mọi vị trí:

Kiểu trong các thư viện cũng dựa vào khả năng suy ra tuple: Promise.all([fetchUser(), fetchPosts()]) cho ra một tuple với mỗi kiểu ứng với một promise đầu vào.

Câu hỏi thường gặp

Tuple trong TypeScript là gì?

Tuple là một kiểu mảng có độ dài cố định, trong đó mỗi vị trí có kiểu riêng: [string, number] là đúng hai phần tử, một chuỗi rồi đến một số. Lúc chạy, nó là một mảng JavaScript bình thường; độ dài và kiểu theo từng vị trí chỉ được kiểm tra lúc biên dịch.

Tuple và mảng trong TypeScript khác nhau thế nào?

Một kiểu mảng như (string | number)[] có độ dài bất kỳ và mọi phần tử có cùng một kiểu (union), nên arr[0] là string | number. Một tuple như [string, number] có độ dài đã biết, t[0] là string, t[1] là number, còn t[2] là lỗi biên dịch.

Làm sao để trả về một tuple từ hàm trong TypeScript?

Chú thích kiểu trả về, function f(): [number, string], hoặc kết thúc biểu thức trả về bằng as const để có một tuple readonly. Không có một trong hai cách đó, return [count, setCount] được suy ra là mảng của một union, như (number | (() => void))[], và destructuring sẽ cho ra các kiểu union.

Phần tử tuple có tên là gì?

Là nhãn đặt cho các vị trí, [name: string, age: number]. Nhãn không thay đổi kiểu hay cách truy cập (vẫn là t[0]), nhưng trình soạn thảo hiển thị chúng khi rê chuột và trong gợi ý tham số của những hàm có tham số được khai báo kiểu là tuple. Phần tử tùy chọn và rest vẫn dùng được với nhãn: [x: number, y?: number], [head: string, ...rest: number[]].

Có thể push vào tuple trong TypeScript không?

Với tuple có thể thay đổi thì có: push vẫn biên dịch được, vì tuple kế thừa các phương thức của mảng, dù việc đó phá vỡ độ dài cố định. Khai báo tuple là readonly (hoặc tạo bằng as const) thì push, pop và phép gán theo chỉ số đều thành lỗi biên dịch.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu