Interface và instanceof
Toán tử instanceof là toán tử của JavaScript để kiểm tra xem object có phải là instance của class hay không. Ở đây sẽ giải thích mối quan hệ giữa toán tử instanceof và interface của TypeScript.
Khác biệt với instanceof của các ngôn ngữ khác
Toán tử instanceof của các ngôn ngữ khác như Java hay PHP có thể sử dụng với interface, nên cần lưu ý khi học TypeScript sau các ngôn ngữ khác. Dưới đây là ví dụ sử dụng toán tử instanceof với interface trong PHP.
Ví dụ toán tử instanceof của PHPphpinterface MyInterface{}class MyClass implements MyInterface{}$a = new MyClass();var_dump($a instanceof MyInterface);//=> bool(true)
Ví dụ toán tử instanceof của PHPphpinterface MyInterface{}class MyClass implements MyInterface{}$a = new MyClass();var_dump($a instanceof MyInterface);//=> bool(true)
Không thể sử dụng instanceof với interface
TypeScript khác với các ngôn ngữ trên, không thể kiểm tra kiểu bằng instanceof tên_interface. Nếu sử dụng tên interface với toán tử instanceof, sẽ xảy ra lỗi compile.
Ví dụ lỗi compile khi sử dụng toán tử instanceof trong TypeScripttsinterfaceMyInterface {}classMyClass implementsMyInterface {}consta = newMyClass ();'MyInterface' only refers to a type, but is being used as a value here.2693'MyInterface' only refers to a type, but is being used as a value here.console .log (a instanceof); MyInterface
Ví dụ lỗi compile khi sử dụng toán tử instanceof trong TypeScripttsinterfaceMyInterface {}classMyClass implementsMyInterface {}consta = newMyClass ();'MyInterface' only refers to a type, but is being used as a value here.2693'MyInterface' only refers to a type, but is being used as a value here.console .log (a instanceof); MyInterface
Lý do là vì interface là tính năng riêng của TypeScript và bị xóa khỏi code khi compile. Interface là thứ ở level type. TypeScript xóa các thứ ở level type khi compile sang JavaScript. Đây là lý do tương tự như type annotation của biến bị xóa khi compile.
Việc bị xóa khi compile có nghĩa là không có thông tin về interface ở đâu cả khi chạy JavaScript. Do đó, instanceof không thể nhận tên interface.
Sử dụng type guard function để kiểm tra interface
Để kiểm tra xem giá trị có tương thích với interface khi runtime hay không, sử dụng type guard function. Type guard function là function nhận giá trị muốn kiểm tra kiểu làm tham số và trả về true hoặc false. Ví dụ, function kiểm tra xem giá trị có phải là kiểu Student interface hay không như sau:
tsinterfaceStudent {name : string;grade : number;}// Type guard function kiểm tra kiểu StudentfunctionisStudent (value : unknown):value isStudent {// Kiểm tra xem giá trị có phải object khôngif (typeofvalue !== "object" ||value === null) {return false;}const {name ,grade } =value asRecord <keyofStudent , unknown>;// Kiểm tra xem property name có phải kiểu string khôngif (typeofname !== "string") {return false;}// Kiểm tra xem property grade có phải kiểu number khôngif (typeofgrade !== "number") {return false;}return true;}
tsinterfaceStudent {name : string;grade : number;}// Type guard function kiểm tra kiểu StudentfunctionisStudent (value : unknown):value isStudent {// Kiểm tra xem giá trị có phải object khôngif (typeofvalue !== "object" ||value === null) {return false;}const {name ,grade } =value asRecord <keyofStudent , unknown>;// Kiểm tra xem property name có phải kiểu string khôngif (typeofname !== "string") {return false;}// Kiểm tra xem property grade có phải kiểu number khôngif (typeofgrade !== "number") {return false;}return true;}
Sử dụng function isStudent này thay cho instanceof để có thể kiểm tra kiểu khi runtime.
tsconsttom : object = {name : "Tom",grade : 2 };if (isStudent (tom )) {tom ;}
tsconsttom : object = {name : "Tom",grade : 2 };if (isStudent (tom )) {tom ;}
Để biết chi tiết về type guard function, xem trang sau:
📄️ Type guard function
Compiler của TypeScript phân tích type của biến tại mỗi vị trí trong control flow như if hay switch, tính năng này được gọi là control flow analysis (phân tích luồng điều khiển).
zod hữu ích cho việc kiểm tra interface phức tạp
Như ví dụ về type guard function, tôi đã trình bày implementation của isStudent ở trên, nhưng khi xem nội dung có thể thấy cần logic kiểm tra kiểu cho từng property. Nếu property ít, implementation của type guard function có thể giữ ngắn trong phạm vi bảo trì được, nhưng có thể tưởng tượng khi property nhiều sẽ trở thành code khó bảo trì.
Trong trường hợp đó, zod rất hữu ích. zod là thư viện kiểm tra cấu trúc object, được tạo cho TypeScript. Với zod, khi định nghĩa cấu trúc object, sẽ có được type guard function kiểm tra cấu trúc. Dưới đây là ví dụ implement isStudent bằng zod:
tsimportz from "zod";// Định nghĩa schema bằng zodconststudentSchema =z .object ({name :z .string (),grade :z .number (),});// Suy luận kiểu của interfacetypeStudent =z .infer <typeofstudentSchema >;// Type guard functionfunctionisStudent (value : unknown):value isStudent {returnstudentSchema .safeParse (value ).success ;}// Kiểm tra kiểuconsttom : object = {name : "Tom",grade : 2 };if (isStudent (tom )) {tom ;}
tsimportz from "zod";// Định nghĩa schema bằng zodconststudentSchema =z .object ({name :z .string (),grade :z .number (),});// Suy luận kiểu của interfacetypeStudent =z .infer <typeofstudentSchema >;// Type guard functionfunctionisStudent (value : unknown):value isStudent {returnstudentSchema .safeParse (value ).success ;}// Kiểm tra kiểuconsttom : object = {name : "Tom",grade : 2 };if (isStudent (tom )) {tom ;}
Với zod, code trở nên declarative, không cần tự viết implementation chi tiết của type guard function. Khi cần type guard function cho interface có nhiều property hoặc interface có cấu trúc lồng nhau, nên xem xét việc sử dụng zod.
Abstract class và instanceof
TypeScript có abstract class tương tự interface. Khác với interface, abstract class có thể sử dụng toán tử instanceof. Lý do là vì abstract class vẫn tồn tại dưới dạng class ngay cả sau khi compile.
tsabstract classAbstractClass {}classConcreteClass extendsAbstractClass {}constobj = newConcreteClass ();console .log (obj instanceofAbstractClass );
tsabstract classAbstractClass {}classConcreteClass extendsAbstractClass {}constobj = newConcreteClass ();console .log (obj instanceofAbstractClass );