Trong bài viết này chúng ta sẽ đi tìm hiểu về Union/Intersection/Conditional type và type guards.
1. Union type
Union Types là những type mang tính chất: EITHER OR (tạm dịch là Hoặc cái này Hoặc cái kia). Để viết Union Types, chúng ta dùng Pipe Symbol (|).
let text: string | string[];
Biến text
có thể nhận kiểu string
hoặc mảng string
.
Ví dụ :
function print(text: string | string[]): string { if (typeof text === 'string') { return text; } // compiler now knows that you can use join // and that variable type is definitely string[] return text.join(' ');
} let x = print('hello text');
let y = print(['hello', 'text', 'array']); // let z = print(5); // Error: Argument of type '5' is not assignable to type 'string | string[]' console.log(x); //"hello text" console.log(y);//"hello text array"
Chúng ta có thể sử dụng union type
với class/interface
interface IStudent { id: string; age: number;
} interface IWorker { companyId: string;
} type IUnionType = IStudent | IWorker; let p: IUnionType = { id: 'ID3241', age: 21
}; // p = 3; // Type '3' is not assignable to type 'IUnionType' p = { companyId: 'cid993'
};
2. Intersection type
Kiểu giao (Intersection Type) trong TypeScript tạo ra một kiểu dữ liệu mới bằng cách kết hợp
hai hay nhiều kiểu dữ liệu lại với nhau. Kiểu dữ liệu mới có tất cả các thuộc tính của các kiểu dữ liệu hiện
có.
Ví dụ : Ta có các interface
interface BusinessPartner { name: string; credit: number;
} interface Identity { id: number; name: string;
} interface Contact { email: string; phone: string;
}
Ta định nghĩa các kiểu giao:
type Employee = Identity & Contact;
type Customer = BusinessPartner & Contact;
Kiểu Employee có chứa tất cả các thuộc tính của kiểu dentity
vàContact
:
type Employee = Identity & Contact; let e: Employee = { id: 100, name: 'John Doe', email: '_@.com', phone: '(408)-897-5684'
};
Và kiểu Customer chứa tất cả các thuộc tính của kiểu BusinessPartner
và Contact
:
type Customer = BusinessPartner & Contact; let c: Customer = { name: 'ABC Inc.', credit: 1000000, email: '_@.com', phone: '(408)-897-5735'
};
Ta cũng có thể tạo ra một kiểu giao mới có chứa tất cả các thuộc tính của kiểu dữ liệu
Identity
,Contact
và BusinessPartner
như sau:
type Employee = Identity & BusinessPartner & Contact; let e: Employee = { id: 100, name: 'John Doe', email: '_@.com', phone: '(408)-897-5684', credit: 1000
};
- Hãy cẩn thận khi kết hợp các
type/interface
có các thuộc tính cùng tên nhưng không cùng kiểu dữ liệu
interface X { c: string; d: string;
} interface Y { c: number; e: string
} type XY = X & Y;
type YX = Y & X; let p: XY;
let q: XY; p.c = 4; // Error: Type '4' is not assignable to type 'string & number'
q.c = 3; // Error: Type '3' is not assignable to type 'string & number' p.c = 'text'; // Error: Type 'text' is not assignable to type 'string & number'
q.c = 'text'; // Error: Type 'text' is not assignable to type 'string & number'
- Bạn sẽ nhận thấy rằng kiểu X và kiểu Y đều có thuộc tính c. Tuy nhiên, loại thuộc tính không giống nhau và khi ta cố gắng gán giá trị cho nó, ta sẽ gặp lỗi từ trình biên dịch.
- Để kết hơp các interface có cùng tên thuộc tính thì kiểu dữ liệu của thuộc tính đó không phải là kiểu nguyên thủy :
interface D { d: boolean; }
interface E { e: string; }
interface F { f: number; } interface A { x: D; }
interface B { x: E; }
interface C { x: F; } type ABC = A & B & C; let abc: ABC = { x: { d: true, e: 'codingblast', f: 3 }
}; console.log('abc:', abc);
3. Type guards
An toàn kiểu (type guard) trong TypeScript cho phép bạn thu hẹp kiểu dữ liệu của biến trong khối lệnh điều kiện.
3.1. Toán tử typeof trong TypeScript
Ví dụ :
type alphanumeric = string | number; function add(a: alphanumeric, b: alphanumeric) { if (typeof a === 'number' && typeof b === 'number') { return a + b; } if (typeof a === 'string' && typeof b === 'string') { return a.concat(b); } throw new Error('Invalid arguments. Both arguments must be either numbers or strings.');
}
Toán tử typeof hoạt động như thế nào:
- Đầu tiên, định nghĩa kiểu
alphanumeric
là kiểu kết hợp có thể chứa một chuỗi hoặc một số . - Tiếp theo, khai báo một hàm có thêm hai biến
a
vàb
với kiểualphanumeric
. - Sau đó, kiểm tra xem kiểu dữ liệu của cả hai đối số có phải là số hay không bằng cách sử
dụng toán tử
typeof
. Nếu có, trả về tổng các đối số. - Ngược lại, kiểm tra xem kiểu dữ liệu của cả hai đối số có phải là chuỗi hay không bằng
cách sử dụng toán tử
typeof
. Nếu có, trả về chuỗi được nối từ hai đối số. - Cuối cùng, đưa ra một lỗi nếu các đối số không phải là số hoặc chuỗi
Trong ví dụ này, TypeScript biết cách sử dụng toán tửtypeof
trong các khối điều kiện. Bên
trong khối if sau, TypeScript nhận ra rằng a và b là những con số.
if (typeof a === 'number' && typeof b === 'number') { return a + b;
}
Tương tự, trong khối if sau , TypeScript xử lý a và b dưới dạng chuỗi, do đó bạn có thể nối chúng thành một:
if (typeof a === 'string' && typeof b === 'string') { return a.concat(b);
}
3.2 Toán tử instanceof trong TypeScript
Ví dụ:
class Customer { isCreditAllowed(): boolean { // ... return true; }
} class Supplier { isInShortList(): boolean { // ... return true; }
} type BusinessPartner = Customer | Supplier; function signContract(partner: BusinessPartner) : string { let message: string; if (partner instanceof Customer) { message = partner.isCreditAllowed() ? 'Sign a new contract with the customer' : 'Credit issue'; } if (partner instanceof Supplier) { message = partner.isInShortList() ? 'Sign a new contract the supplier' : 'Need to evaluate further'; } return message;
}
Toán tử instanceof hoạt động như thế nào:
- Đầu tiên, khai báo các lớp
Customer
vàupplier
. - Thứ hai, tạo bí danh kiểu
BusinessPartner
là kiểu kết hợp củaCustomer
vàSupplier
. - Thứ ba, khai báo một hàm
signContract()
chấp nhận một tham số với kiểuBusinessPartner
. - Cuối cùng, hãy kiểm tra xem đối số có phải là một thể hiện của
Customer
hoặcSupplier
không và sau đó cung cấp logic tương ứng.
Bên trong khối if sau, TypeScript biết rằng partner là một thể hiện của kiểuCustomer
do toán
tử instanceof
:
if (partner instanceof Customer) { message = partner.isCreditAllowed() ? 'Sign a new contract with the customer' : 'Credit issue';
}
Tương tự như vậy, TypeScript biết rằng partner là một thể hiện Supplier bên trong khối if sau :
if (partner instanceof Supplier) { message = partner.isInShortList() ? 'Sign a new contract with the supplier' : 'Need to evaluate further';
}
Khi câu lệnh if thu hẹp một kiểu dữ liệu, TypeScript biết rằng bên trong lệnh else không phải là kiểu dữ liệu đó mà là kiểu dữ liệu khác. Ví dụ:
function signContract(partner: BusinessPartner): string { let message: string; if (isCustomer(partner)) { message = partner.isCreditAllowed() ? 'Sign a new contract with the customer' : 'Credit issue'; } else { message = partner.isInShortList() ? 'Sign a new contract with the supplier' : 'Need to evaluate further'; } return message;
}
4.Conditional Types
Từ TypeScript version 2.8 thì TypeScript cung cấp cho developers Conditional Types. Đây là 1 type khá đặc biệt, giúp chúng ta tạo được những type dựa theo điều kiện. Ví dụ:
T extends U ? X : Y
Dòng code trên có thể hiểu là: khi T có thể gán được cho U thì sẽ trả về type X, còn không thì trả về type Y.
function fn<T extends boolean>(x: T): T extends true ? string : number { // do something
} const x = fn(true); // type của giá trị trả về của fn() sẽ có type là string | number
Conditional Types đơn giản chỉ có vậy, nhưng nếu biết cách sử dụng, mình có thể tạo được những types rất “robust” và phục vụ được việc develop rất nhiều.