Menu

Template literal type trong TypeScript: cú pháp và ví dụ

Template literal type tạo ra string literal type bằng đúng cú pháp backtick của template string trong JavaScript: on${Capitalize<E>}. Tìm hiểu cú pháp, cách union nhân lên, Uppercase và Capitalize, các mẫu như ${number}px, getter với mapped type, và phân tích chuỗi bằng infer.

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.

Template literal type tạo ra string literal type bằng đúng cú pháp backtick của template string trong JavaScript. `on${Capitalize<"click" | "focus">}` là kiểu "onClick" | "onFocus", do trình biên dịch tính ra:

Template literal type chỉ tồn tại lúc biên dịch. Chúng kiểm tra chuỗi literal và các giá trị có kiểu trong lúc bạn viết code; chúng không thêm gì vào đầu ra JavaScript. Bảng handler ở trên dùng Record để bắt buộc mỗi tên phải có một hàm.

Cú pháp

Bên trong dấu backtick, bạn viết văn bản literal và các placeholder ${...}. Một placeholder chứa một kiểu, không phải một giá trị: một literal type kiểu string, number, bigint hoặc boolean, một union của chúng, hoặc một trong các kiểu rộng string, number, bigint, boolean, null và undefined.

Một placeholder với kiểu rộng như string hay number tạo ra một mẫu: kiểu vẫn giữ nguyên là `hello ${string}` và mọi chuỗi khớp mẫu đều được chấp nhận. Một placeholder với union hữu hạn, như boolean, được mở rộng thành từng thành viên của nó.

Union nhân lên

Với nhiều union, kết quả là mọi tổ hợp:

Ba kích thước nhân hai tông màu cho ra sáu thành viên. Số lượng tăng rất nhanh: năm placeholder, mỗi cái chứa một union mười chữ cái, sẽ là 100.000 thành viên, và TypeScript từ chối với error TS2590: Expression produces a union type that is too complex to represent. Hãy dùng một placeholder rộng như ${string} khi bạn không cần từng giá trị chính xác.

Uppercase, Lowercase, Capitalize, Uncapitalize

Bốn kiểu có sẵn đổi chữ hoa chữ thường của string literal type. Chúng là kiểu nội tại (intrinsic): được cài đặt bên trong trình biên dịch, không viết bằng TypeScript.

KiểuĐầu vàoKết quả
Uppercase<S>"hello world""HELLO WORLD"
Lowercase<S>"Content-Type""content-type"
Capitalize<S>"hello world""Hello world"
Uncapitalize<S>"UserName""userName"

Chúng chỉ thay đổi kiểu. Để tạo chuỗi tương ứng lúc chạy, bạn vẫn gọi toUpperCase() hoặc tự cắt chuỗi rồi viết hoa, và báo cho TypeScript biết kết quả có kiểu chính xác:

Cần dùng as vì toUpperCase() được khai báo là trả về string thông thường. Chữ ký của hàm là thứ nơi gọi nhìn thấy, nên capitalize("report") có literal type "Report".

Mẫu chuỗi: ${number}px và các dạng tương tự

Một kiểu mẫu chấp nhận mọi chuỗi có hình dạng cho trước. Nó tiện cho giá trị CSS, id và khóa có tiền tố biết trước:

${number} chấp nhận mọi chuỗi mà JavaScript đọc được thành số, lỏng hơn bạn tưởng: "-3px", "1e3px" và "0x10px" đều qua được kiểm tra kiểu. Hãy coi các mẫu này là cách chặn lỗi gõ sai trong literal, không phải phép kiểm tra hợp lệ đầy đủ.

Template literal với mapped type

Template literal type hữu ích nhất khi làm mệnh đề as của một mapped type, nơi chúng sinh ra tên thuộc tính từ những tên thuộc tính khác:

string & K chỉ giữ lại các khóa kiểu chuỗi, vì Capitalize không nhận số hay symbol. Mỗi callback nhận kiểu tham số từ chính thuộc tính mà nó theo dõi.

Phân tích chuỗi bằng infer

Trong một conditional type, template literal có thể khớp với một chuỗi và bắt lấy từng phần của nó bằng infer. Ví dụ này lấy ra tên các tham số từ một mẫu route:

Bỏ postId trong lời gọi hàm, trình biên dịch sẽ báo là thiếu.

Biểu thức template bị mở rộng thành string

Một biểu thức template string trong code thông thường có kiểu string, kể cả khi mọi phần của nó đều là literal type. Thêm as const để giữ template literal type:

Không có as const, gán loose cho `log:${Level}` sẽ thất bại, vì string có thể là bất cứ chuỗi nào.

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

Template literal type trong TypeScript là gì?

Là string literal type được viết bằng dấu backtick và placeholder ${...}, giống template string của JavaScript nhưng ở cấp kiểu. type Greeting = `hello ${string}` chấp nhận mọi chuỗi bắt đầu bằng hello , và `on${Capitalize<"click">}` là literal type "onClick".

Chuyện gì xảy ra khi đặt một union vào template literal type?

Template được mở rộng cho từng thành viên, và với nhiều union bạn sẽ có mọi tổ hợp. `${"sm" | "lg"}-${"red" | "blue"}` là "sm-red" | "sm-blue" | "lg-red" | "lg-blue". Các tổ hợp quá lớn sẽ thất bại với lỗi TS2590.

Uppercase, Lowercase, Capitalize và Uncapitalize làm gì?

Đây là các kiểu có sẵn dùng để biến đổi string literal type: Uppercase<"id"> là "ID", Lowercase<"ID"> là "id", Capitalize<"name"> là "Name" và Uncapitalize<"Name"> là "name". Chúng chỉ thay đổi kiểu; để đổi một chuỗi lúc chạy, bạn vẫn gọi toUpperCase() và các phương thức tương tự.

Template literal type có kiểm tra chuỗi lúc chạy không?

Không. Như mọi kiểu trong TypeScript, chúng bị xóa đi, nên chỉ kiểm tra chuỗi literal và các giá trị có kiểu lúc biên dịch. Một chuỗi đến lúc chạy, từ JSON hay từ đầu vào của người dùng, chỉ là string cho đến khi bạn tự viết code kiểm tra nó.

Vì sao template string của tôi có kiểu string thay vì literal type?

Một biểu thức template như `on${event}` bị mở rộng thành string khi gán cho một biến. Thêm as const (`on${event}` as const) hoặc chú thích kiểu đích, TypeScript sẽ giữ template literal type, ví dụ "onclick" | "onfocus".

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