Menu

Switch case trong TypeScript: thu hẹp kiểu và kiểm tra đủ

Câu lệnh switch trong TypeScript: cú pháp, cách mỗi case thu hẹp một union type, switch kiểm tra đủ mọi trường hợp để không biên dịch được khi thiếu case, mẫu switch (true), fallthrough và phạm vi khối lệnh.

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.

Câu lệnh switch trong TypeScript là switch của JavaScript có thêm kiểm tra kiểu. Nó so sánh một giá trị với từng case bằng ===, chạy nhánh khớp, và dừng ở break hoặc return. TypeScript thu hẹp kiểu của giá trị đang xét bên trong mỗi case và có thể kiểm tra rằng mọi giá trị có thể xảy ra đều đã được xử lý.

Các nhãn xếp chồng (case "sat": case "sun":) dùng chung một nhánh. return kết thúc hàm, nên không cần break sau nó.

Cú pháp

switch (expression) {
    case value1:
        // runs when expression === value1
        break;
    case value2:
    case value3:
        // runs for value2 or value3
        break;
    default:
        // runs when nothing else matched
}
  • Việc so khớp dùng phép so sánh bằng nghiêm ngặt, ===: case 1 không khớp với chuỗi "1".
  • Không có break, return hoặc throw, chương trình sẽ chạy tiếp vào case kế tiếp (fallthrough).
  • default là tùy chọn và có thể đặt ở bất cứ đâu, dù theo thông lệ nó đứng cuối.
  • Một giá trị case mà TypeScript chứng minh được là không bao giờ khớp sẽ gây lỗi TS2678. Với một tham số có kiểu "a" | "b", case "c": báo Type '"c"' is not comparable to type '"a" | "b"'., nhờ vậy bắt được lỗi gõ sai trong nhãn case.

Thu hẹp kiểu trong từng case

Trong mỗi case, TypeScript biết giá trị nào đã khớp và thu hẹp kiểu tương ứng. Điều này hữu ích nhất với discriminated union: switch trên thuộc tính nhãn dùng chung, và mỗi case thấy đúng biến thể khớp cùng các thuộc tính riêng của nó.

shape.radius chỉ biên dịch được trong case "circle". Ra ngoài case đó, shape có thể là một hình chữ nhật, vốn không có radius. Không có default, nhưng hàm vẫn qua được bước kiểm tra kiểu với kiểu trả về number, vì TypeScript thấy ba case đã bao quát mọi kind. Các mẫu khác dựa trên kỹ thuật này có ở trang discriminated unions.

Switch kiểm tra đủ với never

Hàm ở trên sẽ không biên dịch được nữa nếu thêm một hình thứ tư mà không có case cho nó (TS2366, thiếu lệnh return). Cách đó chỉ hiệu quả khi hàm trả về một giá trị. Để có một bảo đảm dùng được cả trong code void và cho thông báo rõ ràng hơn, hãy thêm một default gán giá trị cho never:

index.ts(14,19): error TS2322: Type '{ kind: "triangle"; base: number; height: number; }' is not assignable to type 'never'.

Sau các case đã xử lý, kiểu duy nhất còn lại của shape là biến thể tam giác, và nó không gán được cho never. Thông báo lỗi chỉ ra chính xác thứ còn thiếu. Thêm case "triangle": return (shape.base * shape.height) / 2; thì default sẽ thấy never, nên khối code biên dịch được và in ra 9. Lệnh throw vẫn bảo vệ trước dữ liệu xấu lúc chạy, như một kind lấy từ JSON mà các kiểu không lường trước.

Nhiều codebase đóng gói phép kiểm tra này trong một hàm hỗ trợ:

Kỹ thuật này cũng dùng được với enum: switch trên giá trị enum và truyền nó vào assertNever trong default.

switch (true) cho khoảng giá trị và điều kiện

switch so sánh các giá trị, nên với khoảng giá trị bạn cần một mẹo: switch trên true và viết một điều kiện trong mỗi case. Từ TypeScript 5.3, các điều kiện đó thu hẹp kiểu giống hệt câu lệnh if.

Các case được kiểm tra theo thứ tự, nên hãy đặt case cụ thể nhất lên đầu. Cách này có dễ đọc hơn if / else if hay không là chuyện sở thích; hành vi thì như nhau.

Fallthrough và break

Một case có code nhưng không có break, return hoặc throw sẽ rơi xuống case kế tiếp. Gần như lúc nào đó cũng là bug. Tùy chọn trình biên dịch noFallthroughCasesInSwitch (không thuộc strict) biến nó thành lỗi TS7029, Fallthrough case in switch., nhưng vẫn cho phép các nhãn rỗng xếp chồng.

Fallthrough có chủ đích như thế này vẫn chạy, nhưng khi bật noFallthroughCasesInSwitch thì phải viết lại, ví dụ bằng các câu lệnh if riêng biệt. Dù sao cách đó thường cũng rõ ràng hơn.

Biến bên trong case

Toàn bộ thân switch là một khối lệnh, nên một const khai báo trong một case sẽ nhìn thấy được ở các case khác (và khai báo lại là lỗi). Bọc một case trong cặp ngoặc nhọn để nó có phạm vi riêng:

Không có cặp ngoặc nhọn, const unit thứ hai là lỗi biên dịch TS2451, Cannot redeclare block-scoped variable 'unit'.

switch hay tra cứu bằng đối tượng

Khi mỗi case chỉ ánh xạ một giá trị sang một giá trị khác, một đối tượng có kiểu Record sẽ ngắn gọn hơn, và TypeScript kiểm tra rằng mọi khóa đều có mặt:

Bỏ sót một khóa là lỗi biên dịch, cho bạn mức kiểm tra đủ giống như phép kiểm tra never. Hãy giữ switch cho những trường hợp chạy logic khác nhau, thu hẹp các biến thể của union, hoặc trả về sớm.

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

Viết câu lệnh switch trong TypeScript như thế nào?

Giống hệt JavaScript: switch (value) { case "a": ...; break; default: ... }. Các case được so sánh bằng ===. TypeScript thêm các bước kiểm tra: một giá trị case không bao giờ khớp được với kiểu đang xét là lỗi (TS2678), và bên trong mỗi case, biến đang xét được thu hẹp về đúng case đó.

Làm sao để switch kiểm tra đủ mọi trường hợp trong TypeScript?

Thêm một default gán giá trị cho một biến có kiểu never: default: { const unreachable: never = value; throw new Error(...) }. Khi mọi thành viên của union đều được xử lý, giá trị ở đó là never và code biên dịch được. Khi thiếu một thành viên, trình biên dịch báo đúng thành viên còn thiếu (TS2322).

Làm sao xử lý nhiều case bằng cùng một đoạn code trong switch TypeScript?

Xếp chồng các nhãn mà không có code ở giữa: case "sat": case "sun": return "weekend";. Các case rỗng sẽ rơi xuống case kế tiếp. Tùy chọn noFallthroughCasesInSwitch chỉ báo những case có code mà không có break hay return, nên các nhãn xếp chồng vẫn được phép.

switch (true) có thu hẹp kiểu trong TypeScript không?

Có, từ TypeScript 5.3. Trong switch (true) { case typeof x === "string": ... }, x được thu hẹp thành string bên trong case đó, giống như trong một câu lệnh if. Đây là cách dễ đọc thay cho một chuỗi if/else if kiểm tra khoảng giá trị hoặc kiểu.

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