Bài 10.3: Kiểu dữ liệu cơ bản trong TypeScript

Chào mừng trở lại với series Lập trình Web Front-end! Sau khi đã làm quen với bức tranh tổng thể về TypeScript và lý do tại sao nó lại là một công cụ mạnh mẽ cho phát triển Front-end hiện đại, bước tiếp theo cực kỳ quan trọng là làm chủ các kiểu dữ liệu cơ bản.

Nếu bạn đã quen với JavaScript, bạn biết rằng nó rất linh hoạt (đôi khi quá linh hoạt!). Một biến có thể chứa một số, rồi sau đó lại chứa một chuỗi mà không gặp vấn đề gì. TypeScript mang đến sự có cấu trúcdự đoán được bằng cách cho phép (hoặc khuyến khích) chúng ta định nghĩa rõ ràng kiểu dữ liệu mà một biến, tham số hàm, hoặc giá trị trả về sẽ chứa. Điều này không chỉ giúp TypeScript Compiler bắt lỗi cho bạn ngay trong quá trình viết code (trước khi code chạy!), mà còn làm cho code của bạn minh bạch, dễ đọcdễ bảo trì hơn rất nhiều.

Hãy cùng khám phá những "viên gạch" nền tảng này của TypeScript nhé!

1. Kiểu Dữ liệu number

Kiểu number trong TypeScript tương tự như trong JavaScript, được dùng để biểu diễn tất cả các giá trị số. Nó bao gồm cả số nguyên (integers) và số thực (floating-point numbers).

Cách khai báo một biến với kiểu number:

let tuoi: number = 30; // Khai báo biến 'tuoi' kiểu number, gán giá trị nguyên
let giaTien: number = 19.99; // Khai báo biến 'giaTien' kiểu number, gán giá trị thực
let tong: number = tuoi + giaTien; // Các phép toán số học cho kết quả là number

console.log(tuoi); // Output: 30
console.log(giaTien); // Output: 19.99
console.log(tong); // Output: 49.99

Giải thích: Ở đây, chúng ta sử dụng dấu hai chấm : sau tên biến (tuoi, giaTien, tong) để chỉ định kiểu dữ liệu của nó là number. TypeScript sau đó sẽ đảm bảo rằng bạn chỉ có thể gán các giá trị số cho các biến này. Nếu bạn cố gắng gán một giá trị không phải số (ví dụ: một chuỗi) cho biến tuoi, TypeScript sẽ báo lỗi ngay khi biên dịch.

2. Kiểu Dữ liệu string

Kiểu string được sử dụng để lưu trữ dữ liệu văn bản, tức là các chuỗi ký tự. TypeScript hỗ trợ cả ba cách khai báo chuỗi mà bạn đã quen thuộc từ JavaScript: dùng dấu ngoặc kép ("), dấu ngoặc đơn ('), và dấu backtick (```) cho template literals.

Cách khai báo một biến với kiểu string:

let ten: string = "Alice"; // Dùng dấu ngoặc kép
let loiChao: string = 'Xin chào!'; // Dùng dấu ngoặc đơn

// Template literal (sử dụng dấu backtick `) cho phép nhúng biến/biểu thức vào chuỗi
let hoTen: string = "Bob Nguyen";
let thongBao: string = `Tên người dùng là: ${hoTen}`;

console.log(ten); // Output: Alice
console.log(loiChao); // Output: Xin chào!
console.log(thongBao); // Output: Tên người dùng là: Bob Nguyen

Giải thích: Biến ten, loiChao, và thongBao được khai báo với kiểu string. Bạn có thể sử dụng bất kỳ loại dấu ngoặc nào để định nghĩa chuỗi. Template literals với dấu backtick (`) và cú pháp${tênBiến}` rất hữu ích khi bạn muốn tạo chuỗi động chứa giá trị của biến.

3. Kiểu Dữ liệu boolean

Kiểu boolean là kiểu đơn giản nhất, chỉ có hai giá trị có thể có: true (đúng) hoặc false (sai). Nó thường được dùng để biểu diễn trạng thái bật/tắt, có/không, hoặc kết quả của các phép so sánh logic.

Cách khai báo một biến với kiểu boolean:

let daDangNhap: boolean = true; // Biến 'daDangNhap' kiểu boolean, gán giá trị true
let coLoi: boolean = false; // Biến 'coLoi' kiểu boolean, gán giá trị false

let isGreaterThanTen: boolean = (15 > 10); // Kết quả của phép so sánh là boolean

console.log(daDangNhap); // Output: true
console.log(coLoi); // Output: false
console.log(isGreaterThanTen); // Output: true

Giải thích: Biến daDangNhapcoLoi rõ ràng thuộc kiểu boolean. Biến isGreaterThanTen nhận giá trị từ kết quả của biểu thức so sánh (15 > 10), mà kết quả này luôn là một giá trị boolean (true hoặc false).

4. Kiểu Dữ liệu nullundefined

Đây là hai kiểu dữ liệu đặc biệt trong JavaScript và cả TypeScript, dùng để biểu thị sự vắng mặt của giá trị.

  • undefined: Một biến có giá trị undefined khi nó đã được khai báo nhưng chưa được gán bất kỳ giá trị nào.
  • null: null biểu thị sự vắng mặt có chủ đích của giá trị. Tức là, bạn cố ý gán null cho một biến để nói rằng biến đó hiện không chứa một giá trị hợp lệ nào.

Lưu ý quan trọng: Trong chế độ strict của TypeScript (rất nên dùng, được bật mặc định trong các dự án mới), nullundefined là hai kiểu riêng biệt và không thể gán cho nhau hoặc cho các kiểu dữ liệu khác như number, string, boolean trừ khi bạn chỉ định rõ ràng (ví dụ: sử dụng Union Types như string | null). Điều này giúp phát hiện lỗi sớm hơn rất nhiều.

Ví dụ:

let bienChuaGanGiaTri; // Khai báo biến nhưng chưa gán
console.log(bienChuaGanGiaTri); // Output: undefined (Đây là mặc định)

let duLieuNguoiDung: string | null = null; // Khai báo có thể là string hoặc null, ban đầu là null
console.log(duLieuNguoiDung); // Output: null

duLieuNguoiDung = "Chi tiết người dùng..."; // Sau đó có thể gán một giá trị string hợp lệ
console.log(duLieuNguoiDung); // Output: Chi tiết người dùng...

// Nếu strict mode bật, dòng dưới sẽ báo lỗi
// let someString: string = null; // Lỗi: Type 'null' is not assignable to type 'string'.

Giải thích: Khi bienChuaGanGiaTri được khai báo mà không gán giá trị ban đầu, nó tự động có giá trị và kiểu là undefined. Biến duLieuNguoiDung được khai báo với kiểu là Union Type string | null, cho phép nó nhận giá trị là một chuỗi hoặc null. Điều này là cách làm chuẩn trong TypeScript khi bạn muốn một biến có thể "không có giá trị" một cách rõ ràng.

5. Kiểu Dữ liệu void

Kiểu void thường được dùng làm kiểu trả về của các hàm mà không trả về bất kỳ giá trị nào. Điều này phổ biến với các hàm chỉ thực hiện một hành động nào đó, ví dụ như ghi log ra console, thay đổi trạng thái, v.v.

Ví dụ:

function inThongDiep(thongDiep: string): void {
    console.log(thongDiep);
    // Hàm này không có lệnh return giá trị nào
}

inThongDiep("Hoàn thành tác vụ!"); // Output: Hoàn thành tác vụ!

let ketQua = inThongDiep("Một thông điệp khác"); // ketQua sẽ có kiểu là void (hay undefined trong JS runtime)
console.log(ketQua); // Output: Một thông điệp khác (in ra từ hàm)
                   // Output: undefined (giá trị của biến ketQua)

Giải thích: Hàm inThongDiep được khai báo với kiểu trả về là : void. Điều này nói cho TypeScript biết rằng hàm này sẽ không return một giá trị có ý nghĩa nào cả. Khi gọi hàm này và gán kết quả cho biến ketQua, biến này sẽ nhận giá trị undefined (đây là cách JavaScript xử lý việc "không trả về gì"), và TypeScript hiểu điều đó thông qua kiểu void.

6. Kiểu Dữ liệu any (Hãy Cẩn Thận!)

Kiểu any là kiểu dữ liệu cho phép bạn gán bất kỳ giá trị nào thuộc bất kỳ kiểu dữ liệu nào cho biến. Khi một biến có kiểu any, TypeScript sẽ không kiểm tra kiểu cho biến đó. Nó giống như bạn đang tạm thời quay về thế giới của JavaScript thuần vậy.

Cách khai báo một biến với kiểu any:

let duLieuDong: any = 10; // Ban đầu gán giá trị number
console.log(duLieuDong); // Output: 10

duLieuDong = "Bây giờ gán giá trị string"; // Gán lại giá trị string
console.log(duLieuDong); // Output: Bây giờ gán giá trị string

duLieuDong = { ten: "Anna" }; // Gán lại giá trị object
console.log(duLieuDong); // Output: { ten: 'Anna' }

// TypeScript sẽ không báo lỗi ở đây, dù có thể gây lỗi lúc chạy
// duLieuDong.toUpperCase(); // Lỗi lúc chạy vì duLieuDong hiện là object!

Giải thích: Biến duLieuDong với kiểu any cho phép chúng ta gán lại các giá trị thuộc các kiểu khác nhau (number, string, object) mà không bị TypeScript báo lỗi trong quá trình biên dịch.

Tại sao lại "Hãy Cẩn Thận!"?

any hữu ích trong các trường hợp cần tương tác với code JavaScript cũ chưa được viết lại bằng TypeScript, hoặc khi bạn đang xử lý dữ liệu có cấu trúc rất động và không thể biết trước kiểu của nó một cách dễ dàng.

Tuy nhiên, lạm dụng any làm mất đi hầu hết lợi ích của TypeScript về kiểm tra kiểu tĩnh. Nó làm giảm khả năng bắt lỗi sớm và giảm tính minh bạch của code. Hãy cố gắng sử dụng các kiểu cụ thể, hoặc các kiểu linh hoạt hơn nhưng vẫn an toàn như unknown (sẽ học sau) bất cứ khi nào có thể thay vì any.

Một số kiểu cơ bản khác

Ngoài các kiểu phổ biến trên, TypeScript còn hỗ trợ một số kiểu cơ bản khác ít gặp hơn trong các tình huống thông thường nhưng vẫn quan trọng:

  • symbol: Dùng để tạo ra các định danh duy nhất và bất biến, thường được sử dụng làm khóa cho các thuộc tính của object để tránh xung đột tên.

    const idMoi: symbol = Symbol('user-id');
    let nguoiDung = {
        [idMoi]: 123, // Sử dụng symbol làm khóa thuộc tính
        ten: "Charlie"
    };
    console.log(nguoiDung[idMoi]); // Output: 123
    // console.log(nguoiDung.idMoi); // Lỗi! Không thể truy cập bằng tên chuỗi thông thường
    
  • bigint: Dùng để biểu diễn các số nguyên rất lớn vượt quá giới hạn an toàn của kiểu number thông thường (là Number.MAX_SAFE_INTEGER, khoảng 2^53).

    let soNguyenLon: bigint = 9007199254740991n; // Khai báo với 'n' ở cuối
    let soLonHon: bigint = soNguyenLon + 1n; // Các phép toán cần thực hiện trên bigint
    console.log(soLonHon); // Output: 9007199254740992n
    

Các kiểu này ít được sử dụng trong các bài toán Front-end cơ bản, nhưng rất hữu ích khi làm việc với các API hoặc dữ liệu cần độ chính xác cao cho các số nguyên lớn.

TypeScript Rất "Thông Minh": Suy Luận Kiểu (Type Inference)

Một điểm tuyệt vời giúp bạn viết code TypeScript gọn gàng hơn là bạn không luôn luôn phải viết rõ kiểu dữ liệu. Trong nhiều trường hợp, TypeScript có thể tự động suy luận ra kiểu dựa trên giá trị bạn gán cho biến khi khai báo.

Ví dụ:

let tenSanPham = "Laptop"; // TypeScript suy luận tenSanPham là kiểu string
// tenSanPham = 12345; // Lỗi! Type 'number' is not assignable to type 'string'.

let soLuongTonKho = 150; // TypeScript suy luận soLuongTonKho là kiểu number
// soLuongTonKho = "hết hàng"; // Lỗi! Type 'string' is not assignable to type 'number'.

let dangHoatDong = true; // TypeScript suy luận dangHoatDong là kiểu boolean

Giải thích: Khi bạn khai báo một biến và gán ngay một giá trị ban đầu, TypeScript đủ thông minh để biết kiểu dữ liệu của giá trị đó và áp dụng kiểu đó cho biến. Bạn vẫn nhận được lợi ích kiểm tra kiểu mà không cần viết thêm : và tên kiểu. Tuy nhiên, việc viết rõ kiểu dữ liệu (type annotation) là cần thiết và khuyến khích trong các trường hợp phức tạp hơn hoặc khi khai báo biến mà chưa gán giá trị ban đầu.

Nắm vững các kiểu dữ liệu cơ bản này là bước đệm vững chắc để bạn tiếp tục khám phá những tính năng mạnh mẽ hơn, phức tạp hơn của TypeScript như Union Types, Intersection Types, Arrays, Tuples, Enums, Interfaces, v.v. Hãy luyện tập sử dụng chúng thường xuyên để làm quen nhé!

Comments

There are no comments at the moment.