Các kiểu dữ liệu đối tượng trong TypeScript

Trong hướng dẫn này, bạn sẽ tìm hiểu về các kiểu dữ liệu đối tượng trong TypeScript và cách sử dụng chúng.

Kiểu đối tượng trong TypeScript

Kiểu object trong  TypeScript đại diện cho tất cả các giá trị không có trong kiểu nguyên thủy.

Sau đây là các kiểu dữ liệu nguyên thủy trong TypeScript:

  • number
  • bigint
  • string
  • boolean
  • null
  • undefined
  • symbol

Sau đây là cách khai báo biến chứa một đối tượng:

let employee: object;

employee = {
    firstName: 'John',
    lastName: 'Doe',
    age: 25,
    jobTitle: 'Web Developer'
};

console.log(employee);

Đầu ra:

{
  firstName: 'John',       
  lastName: 'Doe',
  age: 25,
  jobTitle: 'Web Developer'
}

Nếu bạn gán lại một giá trị kiểu nguyên thủy cho đối tượng employee, bạn sẽ gặp lỗi:

employee = "Jane";

Lỗi:

error TS2322: Type '"Jane"' is not assignable to type 'object'.

Đối tượng employee có kiểu dữ liệu object với một danh sách các thuộc tính cố định. Nếu bạn cố gắng truy cập một thuộc tính không tồn tại trên đối tượng employee, bạn cũng sẽ gặp lỗi:

console.log(employee.hireDate);

Lỗi:

error TS2339: Property 'hireDate' does not exist on type 'object'.
Lưu ý rằng câu lệnh trên hoạt động hoàn toàn bình thường trong JavaScript và giá trị undefined được trả về.

Để chỉ định rõ ràng các thuộc tính của đối tượng employee, trước tiên bạn sử dụng cú pháp sau để khai báo đối tượng employee:

let employee: {
    firstName: string;
    lastName: string;
    age: number;
    jobTitle: string;
};

Và sau đó bạn gán một đối tượng với các thuộc tính được mô tả cho đối tượng employee như sau:

employee = {
    firstName: 'John',
    lastName: 'Doe',
    age: 25,
    jobTitle: 'Web Developer'
};

Hoặc bạn có thể kết hợp cả hai cú pháp trong cùng một câu lệnh như sau:

let employee: {
    firstName: string;
    lastName: string;
    age: number;
    jobTitle: string;
} = {
    firstName: 'John',
    lastName: 'Doe',
    age: 25,
    jobTitle: 'Web Developer'
};

Kiểu object vs Object

TypeScript có một kiểu dữ liệu khác được gọi là Object với chữ O được viết hoa. Điều quan trọng là phải hiểu sự khác biệt giữa chúng.

Kiểu object đại diện cho tất cả các giá trị phi nguyên thủy trong khi kiểu Object mô tả các chức năng của tất cả các đối tượng.

Ví dụ, kiểu Object có các phương thức toString()valueOf() có thể được truy cập bởi bất kỳ đối tượng nào.

Kiểu empty {}

TypeScript có một kiểu khác được gọi là kiểu empty được ký hiệu là {}, khá giống với kiểu đối tượng.

Kiểu empty {} mô tả một đối tượng không có thuộc tính của riêng nó. Nếu bạn cố gắng truy cập một thuộc tính trên đối tượng như vậy, TypeScript sẽ báo lỗi khi biên dịch:

let vacant: {};
vacant.firstName = 'John';

Lỗi:

error TS2339: Property 'firstName' does not exist on type '{}'.

Nhưng bạn có thể truy cập tất cả các thuộc tính và phương thức được khai báo trên kiểu Object, có sẵn trên đối tượng thông qua prototype:

let vacant: {} = {};

console.log(vacant.toString());

Đầu ra:

[object Object]

Kiểu mảng trong TypeScript

Kiểu array trong TypeScript là một danh sách dữ liệu có thứ tự. Để khai báo một mảng chứa các giá trị của một kiểu dữ liệu cụ thể, bạn sử dụng cú pháp sau:

let arrayName: type[];

Ví dụ, phần sau khai báo một mảng các giá trị kiểu chuỗi:

let skills: string[];

Và bạn có thể thêm một hoặc nhiều chuỗi vào mảng như sau:

skills[0] = "Problem Solving";
skills[1] = "Programming";

Hoặc sử dụng phương thức push() như sau:

skills.push('Software Design');

Phần sau khai báo một biến và gán một mảng chuỗi cho nó:

let skills = ['Problem Sovling','Software Design','Programming'];

Trong ví dụ này, TypeScript suy ra kiểu dữ liệu của biến skills là một mảng kiểu chuỗi. Nó tương đương như sau:

let skills: string[];
skills = ['Problem Sovling','Software Design','Programming'];

Khi bạn định nghĩa một mảng có kiểu dữ liệu cụ thể, TypeScript sẽ ngăn bạn thêm các giá trị không tương thích vào mảng.

Điều sau sẽ gây ra lỗi:

skills.push(100);

Bởi vì chúng ta đang cố gắng thêm một số vào mảng chuỗi.

Lỗi:

Argument of type 'number' is not assignable to parameter of type 'string'.

Khi bạn truy xuất một phần tử từ mảng, TypeScript có thể thực hiện suy luận kiểu dữ liệu. Ví dụ:

let skill = skills[0];
console.log(typeof(skill));

Đầu ra:

string

Trong ví dụ này, chúng tôi truy xuất phần tử đầu tiên của mảng skills và gán nó cho biến skill.

Vì một phần tử trong mảng chuỗi là một chuỗi, nên TypeScript suy ra kiểu của biến skillkiểu chuỗi như được hiển thị trong đầu ra.

Các thuộc tính và phương thức của mảng trong TypeScript

Mảng trong TypeScript có thể truy cập các thuộc tính và phương thức của JavaScript. Ví dụ: phần sau sử dụng thuộc tính length để lấy số phần tử trong một mảng:

let series = [1, 2, 3];
console.log(series.length); // 3

Và bạn có thể sử dụng tất cả các phương thức mảng hữu ích như forEach(), map(), reduce(), và filter(). Ví dụ:

let series = [1, 2, 3];
let doubleIt = series.map(e => e* 2);
console.log(doubleIt);

Đầu ra:

[ 2, 4, 6 ]

Lưu trữ giá trị của các kiểu dữ liệu hỗn hợp

Phần sau minh họa cách khai báo một mảng chứa cả chuỗi và số:

let scores = ['Programming', 5, 'Software Design', 4];

Trong trường hợp này, TypeScript suy luận kiểu dữ liệu của biến scores là một mảng kiểu string | number.

Nó tương đương như sau:

let scores : (string | number)[];
scores = ['Programming', 5, 'Software Design', 4];

Kiểu Tuple trong TypeScript

Tuple hoạt động giống như một mảng với một số bổ sung:

  • Số lượng phần tử trong Tuple được cố định.
  • Kiểu dữ liệu của phần tử đã được chỉ định trước và không cần phải giống nhau.

Ví dụ: bạn có thể sử dụng Tuple để mô tả một giá trị dưới dạng một cặp stringnumber:

let skill: [string, number];
skill = ['Programming', 5];

Thứ tự của các giá trị trong Tuple rất quan trọng. Nếu bạn thay đổi thứ tự các giá trị của Tuple skill thành [5, "Programming"], bạn sẽ gặp lỗi:

let skill: [string, number];
skill = [5, 'Programming'];

Lỗi:

error TS2322: Type 'string' is not assignable to type 'number'.

Vì lý do này, bạn nên sử dụng các bộ dữ liệu có liên quan đến nhau theo một thứ tự cụ thể.

Ví dụ: bạn có thể sử dụng Tuple để định nghĩa màu RGB luôn có dạng ba số như sau:

(r, g, b)

Ví dụ:

let color: [number, number, number] = [255, 0, 0];

Các giá trị color[0], color[1]color[2] sẽ được ánh xạ tới các giá trị màu Red, GreenBlue.

Phần tử Tuple tùy chọn

Kể từ TypeScript 3.0, Tuple có thể có các phần tử tùy chọn được chỉ định bằng cách sử dụng hậu tố dấu hỏi (?).

Ví dụ: bạn có thể định nghĩa một bộ RGBA với giá trị kênh alpha tùy chọn như sau:

let bgColor, headerColor: [number, number, number, number?];
bgColor = [0, 255, 255, 0.5];
headerColor = [0, 255, 255];
Lưu ý rằng RGBA xác định màu sắc bằng cách sử dụng mô hình màu đỏ, xanh lá cây, xanh lam và alpha. Alpha xác định độ mờ của màu.

Kiểu enum trong TypeScript

Enum là gì?

Enum là một nhóm các giá trị hằng số được đặt tên. Enum là viết tắt của kiểu liệt kê.

Để định nghĩa một enum, bạn làm theo các bước sau:

  • Đầu tiên, sử dụng từ khóa enum theo sau là tên của enum.
  • Sau đó, xác định các giá trị hằng số cho enum.

Sau đây là cú pháp để xác định một enum:

enum name {constant1, constant2, ...};

Trong cú pháp này, constant1, constant2, vv, cũng được gọi là các thành viên của enum.

Ví dụ về kiểu enum trong TypeScript

Ví dụ sau tạo một enum đại diện cho các tháng trong năm:

enum Month {
    Jan,
    Feb,
    Mar,
    Apr,
    May,
    Jun,
    Jul,
    Aug,
    Sep,
    Oct,
    Nov,
    Dec
};

Trong ví dụ này, tên enum là Month và các giá trị hằng số là Jan, Feb, Mar, vv.

Phần sau khai báo một hàm sử dụng enum Month làm kiểu dữ liệu cho tham số month:

function isItSummer(month: Month) {
    let isSummer: boolean;
    switch (month) {
        case Month.Jun:
        case Month.Jul:
        case Month.Aug:
            isSummer = true;
            break;
        default:
            isSummer = false;
            break;
    }
    return isSummer;
}

Và bạn có thể thực thi nó như sau:

console.log(isItSummer(Month.Jun)); // true

Ví dụ này sử dụng các giá trị Jan, Feb, Mar, ... trong enum thay cho các giá trị 1, 2, 3, ... Điều này làm cho đoạn code rõ ràng hơn.

Cách hoạt động của Enum trong TypeScript

Một best practice là sử dụng các giá trị không đổi được xác định bởi các enum trong code.

Tuy nhiên, ví dụ sau truyền một số thay vì một enum vào hàm isItSummer(). Và nó hoạt động.

console.log(isItSummer(6)); // true

Ví dụ này sử dụng một số (6) thay vì một hằng số được định nghĩa bởi enum Month. Và nó hoạt động.

Hãy kiểm tra mã Javascript được tạo của enum Month:

var Month;
(function (Month) {
    Month[Month["Jan"] = 0] = "Jan";
    Month[Month["Feb"] = 1] = "Feb";
    Month[Month["Mar"] = 2] = "Mar";
    Month[Month["Apr"] = 3] = "Apr";
    Month[Month["May"] = 4] = "May";
    Month[Month["Jun"] = 5] = "Jun";
    Month[Month["Jul"] = 6] = "Jul";
    Month[Month["Aug"] = 7] = "Aug";
    Month[Month["Sep"] = 8] = "Sep";
    Month[Month["Oct"] = 9] = "Oct";
    Month[Month["Nov"] = 10] = "Nov";
    Month[Month["Dec"] = 11] = "Dec";
})(Month || (Month = {}));

Và bạn có thể xuất biến kiểu Month ra console:

{
  '0': 'Jan', 
  '1': 'Feb', 
  '2': 'Mar', 
  '3': 'Apr', 
  '4': 'May', 
  '5': 'Jun', 
  '6': 'Jul', 
  '7': 'Aug', 
  '8': 'Sep', 
  '9': 'Oct', 
  '10': 'Nov',
  '11': 'Dec',
  Jan: 0,     
  Feb: 1,     
  Mar: 2,     
  Apr: 3,     
  May: 4,
  Jun: 5,
  Jul: 6,
  Aug: 7,
  Sep: 8,
  Oct: 9,
  Nov: 10,
  Dec: 11
}

Như bạn có thể thấy rõ ràng từ đầu ra, một enum TypeScript là một đối tượng trong JavaScript. Đối tượng này có các thuộc tính được đặt tên được khai báo trong enum. Ví dụ, Jan0Feb1.

Đối tượng được tạo cũng có các khóa với các giá trị chuỗi số đại diện cho các hằng số được đặt tên.

Đó là lý do tại sao bạn có thể truyền một số vào hàm chấp nhận một enum. Nói cách khác, một thành viên enum vừa là một số vừa là một hằng số xác định.

Chỉ định giá trị cho các thành viên của enum

TypeScript xác định giá trị của thành viên enum dựa trên thứ tự của thành viên đó xuất hiện trong định nghĩa enum (mặc định: thành viên đầu tiên có giá trị là 0). Ví dụ: trong enum Month thì thành viên Jan có giá trị 0, Feb có giá trị 1, v.v.

Bạn có thể chỉ định rõ ràng các số cho các thành viên của một enum như sau:

enum Month {
    Jan = 1,
    Feb,
    Mar,
    Apr,
    May,
    Jun,
    Jul,
    Aug,
    Sep,
    Oct,
    Nov,
    Dec
};

Trong ví dụ này, giá trị của phần tử Jan sẽ là 1 thay vì 0. Giá trị Feb sẽ là 2 và giá trị của Mar là 3, v.v.

Khi nào sử dụng enum

Bạn nên sử dụng một enum khi bạn:

  • Có một tập hợp nhỏ các giá trị cố định có liên quan chặt chẽ với nhau.
  • Và những giá trị này được biết tại thời điểm biên dịch.

Ví dụ: bạn có thể sử dụng một enum cho trạng thái phê duyệt:

enum ApprovalStatus {
    draft,
    submitted,
    approved,
    rejected
};

Sau đó, bạn có thể sử dụng enum ApprovalStatus như sau:

const request =  {
    id: 1,
    status: ApprovalStatus.approved,
    description: 'Please approve this request'
};

if(request.status === ApprovalStatus.approved) {
    // send an email
    console.log('Send email to the Applicant...');
}

Kiểu any trong TypeScript

Đôi khi, bạn có thể cần lưu trữ một giá trị trong một biến. Nhưng bạn không biết kiểu dữ liệu của nó tại thời điểm viết chương trình. Và giá trị không xác định có thể đến từ API của bên thứ ba hoặc đầu vào của người dùng.

Trong trường hợp này, bạn muốn chọn không kiểm tra kiểu dữ liệu và cho phép giá trị vượt qua kiểm tra lúc biên dịch.

Để làm như vậy, bạn sử dụng kiểu dữ liệu any. Kiểu dữ liệu any cho phép bạn gán một giá trị của bất kỳ kiểu dữ liệu nào cho một biến:

// json may come from a third-party API
const json = `{"latitude": 10.11, "longitude":12.12}`;

// parse JSON to find location
const currentLocation = JSON.parse(json);
console.log(currentLocation);

Đầu ra:

{ latitude: 10.11, longitude: 12.12 }

Trong ví dụ này, biến currentLocation được gán cho một đối tượng do hàm JSON.parse() trả về.

Tuy nhiên, khi bạn sử dụng biến currentLocation để truy cập các thuộc tính, TypeScript cũng sẽ không thực hiện bất kỳ kiểm tra nào:

console.log(currentLocation.x);

Đầu ra:

undefined

Trình biên dịch TypeScript không phàn nàn hoặc đưa ra bất kỳ lỗi nào.

Kiểu dữ liệu any cung cấp cho bạn một cách để làm việc với codebase JavaScript. Nó cho phép bạn chọn tham gia dần dần và chọn không kiểm tra kiểu dữ liệu trong quá trình biên dịch. Do đó, bạn có thể sử dụng kiểu any để chuyển một dự án JavaScript sang TypeScript.

Kiểu dữ liệu không rõ ràng trong TypeScript

Nếu bạn khai báo một biến mà không chỉ định kiểu dữ liệu, TypeScript sẽ giả định rằng bạn sử dụng kiểu any. Tính năng này được gọi là suy luận kiểu dữ liệu. Về cơ bản, TypeScript sẽ đoán kiểu dữ liệu của biến. Ví dụ:

let result;

Trong ví dụ này, TypeScript suy ra kiểu dữ liệu cho bạn. Thực hành này được gọi là kiểu dữ liệu không rõ ràng (implicit type).

Lưu ý rằng để tắt tính năng nhập ẩn cho anykiểu, bạn thay đổi noImplicitAnytùy chọn trong tsconfig.jsontệp thành true. Bạn sẽ tìm hiểu thêm về điều này tsconfig.jsontrong hướng dẫn sau.

Kiểu any vs object trong TypeScript

Nếu bạn khai báo một biến với kiểu object, bạn cũng có thể gán cho nó bất kỳ giá trị nào.

Tuy nhiên, bạn không thể gọi một phương thức trên nó ngay cả khi phương thức đó thực sự tồn tại. Ví dụ:

let result: any;
result = 10.123;
console.log(result.toFixed());
result.willExist(); //

Trong ví dụ này, trình biên dịch TypeScript không đưa ra bất kỳ cảnh báo nào ngay cả khi phương thức willExist() không tồn tại tại thời điểm biên dịch vì phương thức willExist() có thể có sẵn trong lúc thực thi.

Tuy nhiên, nếu bạn thay đổi kiểu của biến result  thành object, trình biên dịch TypeScript sẽ xuất hiện lỗi:

let result: object;
result = 10.123;
result.toFixed();

Lỗi:

error TS2339: Property 'toFixed' does not exist on type 'object'.

Tóm lược

  • Kiểu object trong  TypeScript đại diện cho bất kỳ giá trị nào không phải là giá trị nguyên thủy.
  • Kiểu Object mô tả các chức năng có sẵn trên tất cả các đối tượng.
  • Kiểu empty {} đề cập đến một đối tượng không có thuộc tính riêng của nó.
  • Trong TypeScript, một mảng là một danh sách các giá trị có thứ tự. Một mảng có thể lưu trữ các giá trị hỗn hợp.
  • Để khai báo mảng của một kiểu dữ liệu cụ thể, bạn sử dụng cú pháp let arr: type[].
  • Tupple trong TypeScript là một mảng có số phần tử cố định và kiểu dữ liệu được chỉ định trước.
  • Enum trong TypeScript là một nhóm các giá trị không đổi.
  • Ẩn bên dưới, một enum là một đối tượng JavaScript với các thuộc tính được đặt tên được khai báo trong định nghĩa enum.
  • Sử dụng enum khi bạn có một tập hợp nhỏ các giá trị cố định có liên quan chặt chẽ và được biết đến tại thời điểm biên dịch.
  • Kiểu any trong TypeScript cho phép bạn lưu trữ một giá trị thuộc bất kỳ kiểu nào. Nó hướng dẫn trình biên dịch bỏ qua việc kiểm tra kiểu dữ liệu.
  • Sử dụng kiểu any để lưu trữ một giá trị mà bạn thực sự không biết kiểu dữ liệu của nó tại thời điểm biên dịch hoặc khi bạn chuyển một dự án JavaScript sang một dự án TypeScript.
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.

Kiểu dữ liệu nâng cao trong TypeScript
Trung Nguyen 08/12/2020
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 .

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.