Kiểu dữ liệu nâng cao trong TypeScript
Trong hướng dẫn này, bạn sẽ tìm hiểu về kiểu giao (intersection type), an toàn kiểu (type guard), ép kiểu (type casting) và xác nhận kiểu (type assertions) trong TypeScript .
Kiểu giao trong TypeScript
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 nhiều kiểu dữ liệu hiện có. Kiểu dữ liệu mới có tất cả các tính năng của các kiểu dữ liệu hiện có.
Để kết hợp các kiểu dữ liệu, bạn sử dụng toán tử &
như sau:
type typeAB = typeA & typeB;
Kiểu dữ liệu typeAB
có tất cả các thuộc tính từ cả hai kiểu dữ liệu typeA
và typeB
.
Lưu ý rằng kiểu kết hợp sử dụng toán tử
|
xác định một biến có thể giữ giá trị của một trong hai kiểu dữ liệutypeA
hoặctypeB
let varName = typeA | typeB; // union type
Giả sử rằng bạn có ba interface: BusinessPartner
, Identity
và Contact
.
interface BusinessPartner {
name: string;
credit: number;
}
interface Identity {
id: number;
name: string;
}
interface Contact {
email: string;
phone: string;
}
Dưới đây định nghĩa hai 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 Identity
và Contact
:
type Employee = Identity & Contact;
let e: Employee = {
id: 100,
name: 'John Doe',
email: 'john.doe@example.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: 'sales@abcinc.com',
phone: '(408)-897-5735'
};
Bạn 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: 'john.doe@example.com',
phone: '(408)-897-5684',
credit: 1000
};
Để ý cả hai kiểu dữ liệu BusinessPartner
và Identity
đều có thuộc tính name
cùng kiểu dữ liệu string
. Nếu chúng không cùng kiểu dữ liệu, thì bạn sẽ có một lỗi.
Thứ tự kiểu dữ liệu
Khi bạn giao các kiểu dữ liệu, thứ tự của các kiểu dữ liệu không quan trọng. Ví dụ:
type typeAB = typeA & typeB;
type typeBA = typeB & typeA;
Trong ví dụ này, kiểu dữ liệu typeAB
và typeBA
có các thuộc tính giống nhau.
An toàn kiểu trong TypeScript
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.
Toán tử typeof
trong TypeScript
Hãy xem ví dụ sau:
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);
}
Toán tử instanceof
trong TypeScript
Tương tự như toán tử typeof
, TypeScript cũng có toán tử instanceof
. 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àSupplier
. - 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ểu Customer
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 (partner instanceof Customer) {
message = partner.isCreditAllowed() ? 'Sign a new contract with the customer' : 'Credit issue';
} else {
// must be Supplier
message = partner.isInShortList() ? 'Sign a new contract with the supplier' : 'Need to evaluate further';
}
return message;
}
Toán tử in
trong TypeScript
Toán tử in
thực hiện kiểm tra an toàn cho sự tồn tại của một thuộc tính trên một đối tượng. Bạn cũng có thể sử dụng nó như một an toàn kiểu. Ví dụ:
function signContract(partner: BusinessPartner) : string {
let message: string;
if ('isCreditAllowed' in partner) {
message = partner.isCreditAllowed() ? 'Sign a new contract with the customer' : 'Credit issue';
} else {
// must be Supplier
message = partner.isInShortList() ? 'Sign a new contract the supplier ' : 'Need to evaluate further';
}
return message;
}
An toàn kiểu do người dùng định nghĩa
An toàn kiểu do người dùng định nghĩa cho phép bạn tạo an toàn kiểu hoặc giúp TypeScript suy ra một kiểu dữ liệu khi bạn sử dụng một hàm.
Hàm an toàn kiểu do người dùng định nghĩa là một hàm chỉ trả về arg is aType
. Ví dụ:
function isCustomer(partner: any): partner is Customer {
return partner instanceof Customer;
}
Trong ví dụ này, isCustomer()
là một hàm an toàn kiểu do người dùng định nghĩa. Bây giờ bạn có thể sử dụng nó như sau:
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;
}
Ép kiểu trong TypeScript
Trong phần này, bạn sẽ tìm hiểu về ép kiểu (type casting) trong TypeScript, cho phép bạn chuyển đổi một biến từ kiểu dữ liệu này sang kiểu dữ liệu khác.
JavaScript không có khái niệm ép kiểu vì các biến có kiểu dữ liệu động. Tuy nhiên, mọi biến trong TypeScript đều có một kiểu dữ liệu. Ép kiểu cho phép bạn chuyển đổi một biến từ kiểu dữ liệu này sang kiểu dữ liệu khác.
Trong TypeScript, bạn có thể sử dụng từ khóa as
hoặc toán tử <>
để ép kiểu.
Ép kiểu bằng từ khóa as
trong TypeScript
Đoạn mã sau đây sẽ tìm phần tử input đầu tiên bằng cách sử dụng phương thức querySelector()
:
let input = document.querySelector('input["type="text"]');
Vì kiểu trả về của phương thức document.querySelector()
là kiểu Element
, đoạn mã sau gây ra lỗi trình biên dịch:
console.log(input.value);
Lý do là thuộc tính value không tồn tại trong kiểu Element. Nó chỉ tồn tại trên kiểu HTMLInputElement
.
Để giải quyết vấn đề này, bạn có thể ép kiểu Element sang kiểu HTMLInputElement
bằng cách sử dụng từ khóa as
như sau:
let input = document.querySelector('input[type="text"]') as HTMLInputElement;
Bây giờ, biến input
có kiểu HTMLInputElement
. Vì vậy, việc truy cập thuộc tính value của nó sẽ không gây ra bất kỳ lỗi nào. Đoạn mã sau hoạt động:
console.log(input.value);
Một cách khác để ép kiểu Element
sang kiểu HTMLInputElement
là khi bạn truy cập thuộc tính như sau:
let enteredText = (input as HTMLInputElement).value;
Lưu ý rằng kiểu
HTMLInputElement
mở rộng (kế thừa) kiểuHTMLElement
. Khi bạn ép kiểuHTMLElement
về kiểuHTMLInputElement
, kiểu ép kiểu này còn được gọi là ép kiểu xuống.
Ví dụ dưới đây cũng có thể thực hiện một ép kiểu xuống. Ví dụ:
let el: HTMLElement;
el = new HTMLInputElement();
Trong ví dụ này, biến el
có kiểu HTMLElement
. Và bạn có thể gán cho nó một thể hiện của kiểu HTMLInputElement
vì kiểu HTMLInputElement
là một lớp con của kiểu HTMLElement
.
Cú pháp để chuyển đổi một biến từ kiểu typeA
thành kiểu typeB
như sau:
let a: typeA;
let b = a as typeB;
Ép kiểu bằng toán tử <>
trong TypeScript
Bên cạnh từ khóa as
, bạn có thể sử dụng toán tử <>
để thực hiện ép kiểu. Ví dụ:
let input = <HTMLInputElement>document.querySelector('input[type="text"]');
console.log(input.value);
Cú pháp để ép kiểu sử dụng toán tử <>
là:
let a: typeA;
let b = <typeB>a;
Xác nhận kiểu trong TypeScript
Trong phần này, bạn sẽ tìm hiểu về xác nhận kiểu (type assertion) trong TypeScript.
Giới thiệu về xác nhận kiểu trong TypeScript
Xác nhận kiểu chỉ dẫn trình biên dịch TypeScript coi một giá trị là một kiểu được chỉ định. Nó sử dụng từ khóa as
để làm như vậy:
expression as targetType
Xác nhận kiểu còn được gọi là thu hẹp kiểu. Nó cho phép bạn thu hẹp một kiểu từ một kiểu kết hợp. Hãy xem hàm đơn giản sau:
function getNetPrice(price: number, discount: number, format: boolean): number | string {
let netPrice = price * (1 - discount);
return format ? `$${netPrice}` : netPrice;
}
Hàm getNetPrice()
chấp nhận các đối số price
, discount
và format
và trả về một giá trị kiểu kết hợp number | string
.
Nếu format
là true
, hàmgetNetPrice()
trả về một giá được định dạng như là một chuỗi. Nếu không, nó trả về giá dưới dạng một số.
Phần sau sử dụng từ khóa as
để hướng dẫn trình biên dịch rằng giá trị được gán cho netPrice
là một chuỗi:
let netPrice = getNetPrice(100, 0.05, true) as string;
console.log(netPrice);
Đầu ra:
$95
Tương tự, phần sau sử dụng từ khóa as
để hướng dẫn trình biên dịch rằng giá trị trả về của hàm getNetPrice()
là một số.
let netPrice = getNetPrice(100, 0.05, false) as number;
console.log(netPrice);
Đầu ra:
95
Lưu ý rằng xác nhận kiểu không thực hiện bất kỳ ép kiểu nào. Nó chỉ cho trình biên dịch biết kiểu mà nó sẽ áp dụng cho một giá trị cho mục đích kiểm tra kiểu dữ liệu.
Cú pháp xác nhận kiểu thay thế
Bạn cũng có thể sử dụng toán tử <>
để xác nhận một kiểu, như sau:
<targetType> value
Ví dụ:
let netPrice = <number>getNetPrice(100, 0.05, false);
Lưu ý rằng bạn không thể sử dụng toán tử
<>
với một số thư viện như React. Vì lý do này, bạn nên sử dụng từ khóaas
cho xác nhận kiểu.
Tóm lược
- Kiểu giao trong TypeScript kết hợp hai hoặc nhiều kiểu dữ liệu để tạo ra một 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ó.
- Thứ tự kiểu dữ liệu không quan trọng khi bạn giao các kiểu dữ liệu.
- An toàn kiểu thu hẹp kiểu dữ liệu của một biến trong một khối lệnh điều kiện.
- Sử dụng toán tử
typeof
vàinstanceof
để triển khai an toàn kiểu trong các khối điều kiện. - Ép kiểu cho phép bạn chuyển đổi một biến từ kiểu dữ liệu này sang kiểu dữ liệu khác.
- Sử dụng từ khóa
as
hoặc toán tử<>
để ép kiểu. - Xác nhận kiểu chỉ thị cho trình biên dịch coi một giá trị là một kiểu được chỉ định.
- Xác nhận kiểu không thực hiện bất kỳ chuyển đổi kiểu nào.
- Xác nhận kiểu sử dụng từ khóa
as
hoặc toán tử<>
.