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 typeAtypeB.

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ệu typeA hoặc typeB
let varName = typeA | typeB; // union type

Giả sử rằng bạn có ba interface: BusinessPartner, IdentityContact.

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 IdentityContact:

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 BusinessPartnerContact:

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, ContactBusinessPartner 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 BusinessPartnerIdentity đề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 typeABtypeBA 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 alphanumerickiể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 ab với kiểu alphanumeric.
  • 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 ab 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ý ab 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 CustomerSupplier.
  • Thứ hai, tạo bí danh kiểu BusinessPartner là kiểu kết hợp của CustomerSupplier.
  • Thứ ba, khai báo một hàm signContract() chấp nhận một tham số với kiểu BusinessPartner.
  • 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ặc Supplier 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ểu HTMLElement. Khi bạn ép kiểu HTMLElement về kiểu HTMLInputElement, 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, discountformat và trả về một giá trị kiểu kết hợp number | string.

Nếu formattrue, 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óa as 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ử typeofinstanceof để 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ử <>.
TypeScript
Bài Viết Liên Quan:
Generic type trong TypeScript
Trung Nguyen 20/12/2020
Generic type trong TypeScript

Trong hướng dẫn này, bạn sẽ tìm hiểu về generic type trong TypeScript cho phép bạn sử dụng các kiểu dữ liệu làm tham số chính thức.

Interface trong TypeScript
Trung Nguyen 06/12/2020
Interface trong TypeScript

Trong hướng dẫn này, bạn sẽ tìm hiểu về interface trong TypeScript, cách định nghĩa, cách mở rộng interface và cách sử dụng chúng để thực thi kiểm tra kiểu dữ liệu.

Class trong TypeScript
Trung Nguyen 05/12/2020
Class trong TypeScript

Trong hướng dẫn này, bạn sẽ tìm hiểu từ A-Z về class (lớp), abstract class, kiểm soát truy cập, thuộc tính chỉ đọc, kế thừa, ... trong TypeScript.

Function trong TypeScript
Trung Nguyen 03/12/2020
Function trong TypeScript

Trong hướng dẫn này, bạn sẽ tìm hiểu về function (hàm) trong TypeScript và cách sử dụng chú thích kiểu để thực thi kiểm tra kiểu dữ liệu cho function.