Kiểu unknown
Kiểu unknown của TypeScript được sử dụng khi không biết kiểu là gì.
Kiểu unknown có thể được gán bất kỳ giá trị nào.
tsletvalue : unknown;value = 1; // OKvalue = "string"; // OKvalue = {name : "Object" }; // OK
tsletvalue : unknown;value = 1; // OKvalue = "string"; // OKvalue = {name : "Object" }; // OK
Kiểu unknown là kiểu any type-safe
Kiểu unknown thường được gọi là "kiểu any type-safe" và được đối chiếu với kiểu any.
Kiểu any có thể được gán cho bất kỳ kiểu biến nào.
tsconstvalue : any = 10;constint : number =value ;constbool : boolean =value ;conststr : string =value ;constobj : object =value ;
tsconstvalue : any = 10;constint : number =value ;constbool : boolean =value ;conststr : string =value ;constobj : object =value ;
Ngược lại, giá trị kiểu unknown không thể được gán cho kiểu cụ thể.
tsconstvalue : unknown = 10;constType 'unknown' is not assignable to type 'number'.2322Type 'unknown' is not assignable to type 'number'.: number = int value ;constType 'unknown' is not assignable to type 'boolean'.2322Type 'unknown' is not assignable to type 'boolean'.: boolean = bool value ;constType 'unknown' is not assignable to type 'string'.2322Type 'unknown' is not assignable to type 'string'.: string = str value ;constType 'unknown' is not assignable to type 'object'.2322Type 'unknown' is not assignable to type 'object'.: object = obj value ;constany : any =value ; // OKconstunknown : unknown =value ; // OK
tsconstvalue : unknown = 10;constType 'unknown' is not assignable to type 'number'.2322Type 'unknown' is not assignable to type 'number'.: number = int value ;constType 'unknown' is not assignable to type 'boolean'.2322Type 'unknown' is not assignable to type 'boolean'.: boolean = bool value ;constType 'unknown' is not assignable to type 'string'.2322Type 'unknown' is not assignable to type 'string'.: string = str value ;constType 'unknown' is not assignable to type 'object'.2322Type 'unknown' is not assignable to type 'object'.: object = obj value ;constany : any =value ; // OKconstunknown : unknown =value ; // OK
Có thể nghĩ rằng việc gán thất bại ngay cả đối với biến int có kiểu number là hơi quá mức, nhưng đây là cách để xử lý kiểu không xác định một cách an toàn.
Ngoài ra, kiểu unknown không cho phép truy cập property và gọi method.
tsconstvalue : unknown = 10;'value' is of type 'unknown'.18046'value' is of type 'unknown'.. value toFixed ();constobj : unknown = {name : "Object" };'obj' is of type 'unknown'.18046'obj' is of type 'unknown'.. obj name ;
tsconstvalue : unknown = 10;'value' is of type 'unknown'.18046'value' is of type 'unknown'.. value toFixed ();constobj : unknown = {name : "Object" };'obj' is of type 'unknown'.18046'obj' is of type 'unknown'.. obj name ;
Chi tiết về sự khác biệt đặc tính giữa any và unknown, vui lòng xem trang sau.
📄️ Sự khác biệt giữa any và unknown
Cả hai kiểu any và unknown đều có thể được gán bất kỳ giá trị nào.
unknown và thu hẹp kiểu
unknown là kiểu không xác định an toàn hơn any, nhưng không thể sử dụng nguyên trạng. Để sử dụng giá trị unknown, cần thu hẹp kiểu.
Để thu hẹp kiểu, sử dụng câu lệnh if có chứa biểu thức điều kiện như typeof hoặc instanceof. Điều này được gọi là type guard. Khi thu hẹp bằng type guard, xử lý sau đó có thể coi là kiểu đã được thu hẹp.
tsconstvalue : unknown = "";// Type guardif (typeofvalue === "string") {// Ở đây, value có thể được coi là kiểu stringconsole .log (value .toUpperCase ());}
tsconstvalue : unknown = "";// Type guardif (typeofvalue === "string") {// Ở đây, value có thể được coi là kiểu stringconsole .log (value .toUpperCase ());}
Trong ví dụ này, biến value có kiểu unknown được xác định là kiểu string trong if bằng typeof, nên có thể sử dụng method toUpperCase() của kiểu string.
Để thu hẹp kiểu, cũng có thể sử dụng hàm type guard.
ts// Hàm type guardfunctionisObject (value : unknown):value is object {return typeofvalue === "object" &&value !== null;}constvalue : unknown = {a : 1,b : 2 };// Type guardif (isObject (value )) {// Ở đây, value có thể được coi là kiểu objectconsole .log (Object .keys (value ));}
ts// Hàm type guardfunctionisObject (value : unknown):value is object {return typeofvalue === "object" &&value !== null;}constvalue : unknown = {a : 1,b : 2 };// Type guardif (isObject (value )) {// Ở đây, value có thể được coi là kiểu objectconsole .log (Object .keys (value ));}
📄️ Phân tích control flow và thu hẹp kiểu bằng type guard
TypeScript có thể thu hẹp kiểu của biến theo luồng xử lý thông qua control flow và type guard.
Thu hẹp kiểu unknown thành kiểu mảng
Khi muốn thu hẹp kiểu unknown thành kiểu mảng, sử dụng Array.isArray(). Thêm vào đó, kiểm tra cả phần tử mảng sẽ làm cho xử lý kiểm tra an toàn hơn.
tsfunctionisNumberArray (value : unknown):value is number[] {if (!Array .isArray (value )) {return false;}returnvalue .every ((e ) => typeofe === "number");}
tsfunctionisNumberArray (value : unknown):value is number[] {if (!Array .isArray (value )) {return false;}returnvalue .every ((e ) => typeofe === "number");}
Thu hẹp kiểu unknown thành kiểu object
Để thu hẹp kiểu unknown thành kiểu object, sử dụng toán tử typeof.
tstypefrom : string;to : string;title : string;subject : string;};functionisEmail (value : unknown):value isreturn typeofvalue !== "object" ||value === null;}
tstypefrom : string;to : string;title : string;subject : string;};functionisEmail (value : unknown):value isreturn typeofvalue !== "object" ||value === null;}
Như vậy, không biết được giá trị có thực sự thỏa mãn kiểu Email hay không. Vì không kiểm tra đến property như from. Để tăng độ chính xác của kiểm tra, cần kiểm tra kiểu của từng property.
tsfunctionisEmail (value : unknown):value isif (typeofvalue !== "object" ||value === null) {return false;}// Kiểm tra từng propertyif (typeofvalue .from !== "string") {return false;}return true;}
tsfunctionisEmail (value : unknown):value isif (typeofvalue !== "object" ||value === null) {return false;}// Kiểm tra từng propertyif (typeofvalue .from !== "string") {return false;}return true;}
Kiểm tra property ở trên thoạt nhìn có vẻ không có vấn đề, nhưng thực tế sẽ xảy ra lỗi compile như sau.
ts// Kiểm tra từng propertyif (typeofProperty 'from' does not exist on type 'object'.2339Property 'from' does not exist on type 'object'.value .!== "string") { from return false;}return true;}
ts// Kiểm tra từng propertyif (typeofProperty 'from' does not exist on type 'object'.2339Property 'from' does not exist on type 'object'.value .!== "string") { from return false;}return true;}
Để tránh điều này, sử dụng type assertion để đưa về gần kiểu Email. Lúc này, type assertion có thể là as Email, nhưng để type-safe hơn, khuyến nghị sử dụng Record của kiểu unknown.
tsfunctionisEmail (value : unknown):value isif (typeofvalue !== "object" ||value === null) {return false;}// Type assertion để đưa value về gần kiểu Emailconstvalue asRecord <keyof// Kiểm tra từng propertyif (typeoffrom !== "string") {return false;}return true;}
tsfunctionisEmail (value : unknown):value isif (typeofvalue !== "object" ||value === null) {return false;}// Type assertion để đưa value về gần kiểu Emailconstvalue asRecord <keyof// Kiểm tra từng propertyif (typeoffrom !== "string") {return false;}return true;}
Lúc này, kiểu Record<keyof Email, unknown> trở thành kiểu có tất cả property của Email là unknown như sau.
tstypeMayBeEmail =Record <keyof
tstypeMayBeEmail =Record <keyof
Cuối cùng, xử lý kiểm tra đã implement kiểm tra tất cả property như sau.
tsfunctionisEmail (value : unknown):value isif (typeofvalue !== "object" ||value === null) {return false;}constvalue asRecord <keyofif (typeoffrom !== "string") {return false;}if (typeofto !== "string") {return false;}if (typeoftitle !== "string") {return false;}return typeofsubject === "string";}
tsfunctionisEmail (value : unknown):value isif (typeofvalue !== "object" ||value === null) {return false;}constvalue asRecord <keyofif (typeoffrom !== "string") {return false;}if (typeofto !== "string") {return false;}if (typeoftitle !== "string") {return false;}return typeofsubject === "string";}
Để thu hẹp an toàn từ kiểu unknown sang kiểu object, cần kiểm tra từng property một. Có thể một số độc giả nhìn ví dụ trên nghĩ rằng việc implement này khá vất vả.
Nếu số property cần kiểm tra nhiều, nên sử dụng thư viện validation sau.
Các thư viện này có thể implement các mục kiểm tra một cách khai báo, giúp giảm chi phí implementation và giảm sai sót trong xử lý kiểm tra.
Mục đích sử dụng unknown
Làm cho giá trị kiểu any an toàn hơn
Ví dụ, giá trị trả về của JSON.parse() là kiểu any. Nếu lấy giá trị trả về như vậy, nếu truy cập vào property không tồn tại, có nguy cơ gặp lỗi runtime.
Do đó, bằng cách chuyển thành kiểu unknown, sẽ dễ dàng nhận ra truy cập vào property không tồn tại tại thời điểm compile.
tsconstdata : unknown =JSON .parse ("...");
tsconstdata : unknown =JSON .parse ("...");
Tránh hạn chế của type assertion
Thông thường, trong type assertion, không thể chỉ định kiểu hoàn toàn khác.
tsconststr = "a";constConversion of type 'string' to type 'number' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.2352Conversion of type 'string' to type 'number' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.num =str as number;
tsconststr = "a";constConversion of type 'string' to type 'number' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.2352Conversion of type 'string' to type 'number' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.num =str as number;
Trong trường hợp như vậy có thể sử dụng kiểu unknown. Vì kiểu unknown có thể type assertion sang bất kỳ kiểu nào, có kỹ thuật là chèn type assertion sang kiểu unknown trước kiểu mục tiêu.
tsconststr = "a";constnum =str as unknown as number;
tsconststr = "a";constnum =str as unknown as number;
Tuy nhiên, type assertion không thực sự cast kiểu của giá trị, mà chỉ làm cho TypeScript nhận diện là kiểu đó, nên vấn đề về type-safe vẫn còn.
Kiểu của giá trị được bắt trong try-catch
TypeScript từ phiên bản 4.4 cho phép chọn exception được ném sẽ được bắt là kiểu any hay kiểu unknown. Tuy nhiên, theo cài đặt mặc định, exception được ném là kiểu any, nên nếu muốn là kiểu unknown, cần thay đổi cài đặt tsconfig.json.
📄️ useUnknownInCatchVariables
Xử lý e trong catch(e) bắt exception là unknown type