Kiểu never
Kiểu never là kiểu đặc biệt của TypeScript có nghĩa là "không có giá trị".
Đặc tính của never
Không thể gán gì vào
Không thể gán bất cứ thứ gì vào kiểu never.
tsconstType '1' is not assignable to type 'never'.2322Type '1' is not assignable to type 'never'.: never = 1; foo
tsconstType '1' is not assignable to type 'never'.2322Type '1' is not assignable to type 'never'.: never = 1; foo
Ngay cả kiểu any cũng không thể gán vào.
tsconstany : any = 1;constType 'any' is not assignable to type 'never'.2322Type 'any' is not assignable to type 'never'.: never = foo any ;
tsconstany : any = 1;constType 'any' is not assignable to type 'never'.2322Type 'any' is not assignable to type 'never'.: never = foo any ;
Chỉ có kiểu never mới có thể gán vào.
tsconstfoo : never = 1 as never;
tsconstfoo : never = 1 as never;
Có thể gán vào bất cứ kiểu nào
Kiểu never có thể được gán vào bất kỳ kiểu nào.
tsconstnev = 1 as never;consta : string =nev ; // Gán OKconstb : string[] =nev ; // Gán OK
tsconstnev = 1 as never;consta : string =nev ; // Gán OKconstb : string[] =nev ; // Gán OK
Không có giá trị nghĩa là gì
"Không có giá trị" của kiểu never nghĩa là gì? Ví dụ, giá trị trả về của hàm luôn gây ra exception. Giá trị trả về không bao giờ có thể lấy được. Do đó, kiểu của giá trị trả về là kiểu never.
tsfunctionthrowError (): never {throw newError ();}
tsfunctionthrowError (): never {throw newError ();}
Hàm không kết thúc cũng có giá trị trả về là kiểu never.
tsfunctionforever (): never {while (true) {} // Vòng lặp vô hạn}
tsfunctionforever (): never {while (true) {} // Vòng lặp vô hạn}
Giá trị không thể tạo ra cũng trở thành kiểu never. Ví dụ, không thể tạo ra giá trị có thể gán cho cả kiểu number và kiểu string. Do đó, intersection type của kiểu number và kiểu string là kiểu never.
tstypeNumberString = number & string;
tstypeNumberString = number & string;
Sự khác biệt giữa kiểu void và kiểu never
Kiểu void có thể gán undefined, nhưng never không thể có giá trị.
tsconstok : void =undefined ;constType 'undefined' is not assignable to type 'never'.2322Type 'undefined' is not assignable to type 'never'.: never = ng undefined ;
tsconstok : void =undefined ;constType 'undefined' is not assignable to type 'never'.2322Type 'undefined' is not assignable to type 'never'.: never = ng undefined ;
Về mặt ý nghĩa, void và never ở giá trị trả về đều giống nhau là không có giá trị trả về. Điểm khác biệt là hàm có kết thúc hay không. void có nghĩa là hàm được thực thi đến cuối. never có nghĩa là xử lý của hàm bị gián đoạn hoặc thực thi mãi mãi.
| Kiểu | Giá trị trả về | Có kết thúc không |
|---|---|---|
void | Không | return hoặc thực thi đến cuối |
never | Không | Bị gián đoạn hoặc thực thi mãi mãi |
Do đó, nếu implementation của hàm có giá trị trả về là never có thể chạy đến cuối, TypeScript sẽ báo lỗi compile.
tsfunctionA function returning 'never' cannot have a reachable end point.2534A function returning 'never' cannot have a reachable end point.func ():never {}
tsfunctionA function returning 'never' cannot have a reachable end point.2534A function returning 'never' cannot have a reachable end point.func ():never {}
Kiểm tra tính đầy đủ (exhaustiveness check) bằng never
Đặc tính không thể gán gì vào của never có thể được ứng dụng cho exhaustiveness check. Exhaustiveness check là việc để compiler kiểm tra xem logic có xử lý hết tất cả các pattern hay không khi xử lý phân nhánh union type.
Ví dụ, có một union type với 3 pattern.
tstypeExtension = "js" | "ts" | "json";
tstypeExtension = "js" | "ts" | "json";
Đây là xử lý phân nhánh chỉ xử lý 2 pattern. Không có tính đầy đủ, nhưng TypeScript không cảnh báo.
Phân nhánh không đầy đủtsfunctionprintLang (ext :Extension ): void {switch (ext ) {case "js":console .log ("JavaScript");break;case "ts":console .log ("TypeScript");break;// Không có phân nhánh cho "json"}}
Phân nhánh không đầy đủtsfunctionprintLang (ext :Extension ): void {switch (ext ) {case "js":console .log ("JavaScript");break;case "ts":console .log ("TypeScript");break;// Không có phân nhánh cho "json"}}
Cơ bản về exhaustiveness check
Để thực hiện exhaustiveness check, gán giá trị cần kiểm tra tính đầy đủ cho kiểu never trong phân nhánh default. Khi đó, TypeScript sẽ cảnh báo lỗi gán.
Phân nhánh có exhaustiveness checktsfunctionprintLang (ext :Extension ): void {switch (ext ) {case "js":console .log ("JavaScript");break;case "ts":console .log ("TypeScript");break;default:constType '"json"' is not assignable to type 'never'.2322Type '"json"' is not assignable to type 'never'.: never = exhaustivenessCheck ext ;break;}}
Phân nhánh có exhaustiveness checktsfunctionprintLang (ext :Extension ): void {switch (ext ) {case "js":console .log ("JavaScript");break;case "ts":console .log ("TypeScript");break;default:constType '"json"' is not assignable to type 'never'.2322Type '"json"' is not assignable to type 'never'.: never = exhaustivenessCheck ext ;break;}}
Exhaustiveness check bằng exception
Khuyến nghị nên định nghĩa class exception cho exhaustiveness check. Class này được thiết kế để nhận kiểu never làm tham số constructor.
Hàm exhaustiveness checktsclassExhaustiveError extendsError {constructor(value : never,message = `Unsupported type: ${value }`) {super(message );}}
Hàm exhaustiveness checktsclassExhaustiveError extendsError {constructor(value : never,message = `Unsupported type: ${value }`) {super(message );}}
Ném exception này trong phân nhánh default. Truyền tham số cần kiểm tra tính đầy đủ vào constructor. Khi làm như vậy, nếu tính đầy đủ không được đáp ứng, TypeScript sẽ cảnh báo lỗi gán.
tsfunctionprintLang (ext :Extension ): void {switch (ext ) {case "js":console .log ("JavaScript");break;case "ts":console .log ("TypeScript");break;default:throw newArgument of type '"json"' is not assignable to parameter of type 'never'.2345Argument of type '"json"' is not assignable to parameter of type 'never'.ExhaustiveError (); ext }}
tsfunctionprintLang (ext :Extension ): void {switch (ext ) {case "js":console .log ("JavaScript");break;case "ts":console .log ("TypeScript");break;default:throw newArgument of type '"json"' is not assignable to parameter of type 'never'.2345Argument of type '"json"' is not assignable to parameter of type 'never'.ExhaustiveError (); ext }}
Có 2 lợi ích khi dùng exception.
- Có thể xử lý
noUnusedLocals - Code có ý thức về runtime
Có thể xử lý noUnusedLocals
Compiler option noUnusedLocals cài đặt có cảnh báo về biến không được sử dụng hay không. Khi option này là true, exhaustiveness check chỉ gán vào biến sẽ gây lỗi compile.
Dù xử lý đủ hết nhưng vẫn bị cảnh báo biến không sử dụngtsfunctionfunc (value : "yes" | "no"): void {switch (value ) {case "yes":console .log ("YES");break;case "no":console .log ("NO");break;default:const'exhaustivenessCheck' is declared but its value is never read.6133'exhaustivenessCheck' is declared but its value is never read.: never = exhaustivenessCheck value ;break;}}
Dù xử lý đủ hết nhưng vẫn bị cảnh báo biến không sử dụngtsfunctionfunc (value : "yes" | "no"): void {switch (value ) {case "yes":console .log ("YES");break;case "no":console .log ("NO");break;default:const'exhaustivenessCheck' is declared but its value is never read.6133'exhaustivenessCheck' is declared but its value is never read.: never = exhaustivenessCheck value ;break;}}
Nếu làm exhaustiveness check bằng exception, lỗi compile về biến không sử dụng sẽ không xảy ra.
Code có ý thức về runtime
Exception là implementation có ý thức hơn về JavaScript sau khi compile. Khi compile code exhaustiveness check bằng gán biến, sẽ sinh ra JavaScript sau.
JavaScript sau khi compile (exhaustiveness check bằng gán biến)tsfunction func(value) {switch (value) {case "yes":console.log("YES");break;case "no":console.log("NO");break;default:const exhaustivenessCheck = value;break;}}
JavaScript sau khi compile (exhaustiveness check bằng gán biến)tsfunction func(value) {switch (value) {case "yes":console.log("YES");break;case "no":console.log("NO");break;default:const exhaustivenessCheck = value;break;}}
Người không biết TypeScript gốc khi nhìn code này, việc gán vào exhaustivenessCheck có ý đồ không rõ ràng. Hơn nữa, exhaustiveness check không được thực hiện tại runtime.
Exhaustiveness check bằng exception có ý đồ rõ ràng ngay cả khi chỉ nhìn code sau compile. Hơn nữa, check cũng được thực hiện tại runtime. Đây là implementation tốt hơn.
JavaScript sau khi compile (exhaustiveness check bằng exception)tsclass ExhaustiveError extends Error {constructor(value, message = `Unsupported type: ${value}`) {super(message);}}function func(value) {switch (value) {case "yes":console.log("YES");break;case "no":console.log("NO");break;default:throw new ExhaustiveError(value);}}
JavaScript sau khi compile (exhaustiveness check bằng exception)tsclass ExhaustiveError extends Error {constructor(value, message = `Unsupported type: ${value}`) {super(message);}}function func(value) {switch (value) {case "yes":console.log("YES");break;case "no":console.log("NO");break;default:throw new ExhaustiveError(value);}}
Chia sẻ kiến thức
never trong TypeScript là kiểu "không có giá trị".
1️⃣Đặc tính 1: Không thể gán gì vào never
2️⃣Đặc tính 2: never có thể gán vào bất cứ thứ gì
💥Có thể dùng cho giá trị trả về của hàm luôn gây exception
👐Khác với void
✅Có thể ứng dụng cho exhaustiveness check
Từ 『Survival TypeScript』