TypeScript là phiên bản cải tiến của JavaScript, giúp cho dự án chặt chẽ trong việc kiểm tra kiểu dữ liệu trong quá trình biên dịch. Đừng lo lắng, nếu bạn đã biết có kiến thức nền tảng về Javascript thì Typescript sẽ không thể làm khó được bạn.
Thậm chí nếu bạn chưa biết về JavaScript, nhưng đã có kiến thức nền tảng về lập trình ở bất kỳ ngôn ngữ nào. Thì mình tin rằng bạn cũng có thể ngấm được syntax cơ bản của TypeScript một cách dễ dàng.
Đây là phần 1, mình sẽ cung cấp các bạn một số syntax cơ bản về các kiểu dữ liệu. Đi kèm theo đó là một số lưu ý nho nhỏ khi sử dụng các kiểu dữ liệu trong TypeScript.
1. Tại sao phải sử dụng TypeScript ?
Câu hỏi “Tại sao phải sử dụng TypeScript?” nó cũng giống như việc bạn đang thắc mắc về lý do tại sao nó ra đời vậy. JavaScript là một ngôn ngữ lập trình OOP, nhưng do tính phức tạp và một số hạn chế, nó chưa thực sự đáp ứng được hoàn toàn những yêu cầu của lập trình OOP.
Mặt khác, TypeScript còn được coi là một trình kiểm tra lỗi kiểu tĩnh ngay tại thời điểm biên dịch, thay vì tại thời điểm chạy (runtime). Nhờ vào điểm này mà dường như TypeScript cung cấp một lớp bảo vệ giúp phát hiện và sửa lỗi kiểu ngay trong quá trình phát triển, làm cho mã trở nên đáng tin cậy và dễ bảo trì hơn.
Đó chính là lý do TypeScript ra đời, với mục tiêu khắc phục những điểm yếu của JavaScript và mang lại một trải nghiệm lập trình OOP mạnh mẽ và hiệu quả hơn.
2. Các kiểu dữ liệu trong TypeScript
2.1 Primitive types (Giá trị nguyên thuỷ)
Tương tự như JavaScript, TypeScript cũng có 7 kiểu dữ liệu nguyên thuỷ (primitive type):
- String: Chuỗi ký tự. Ví dụ: "200", "Lab"
- Number: Số, bao gồm cả số nguyên và số thập phân.
- Boolean: Giá trị đúng (
true
) hoặc sai (false
). - Bigint: Kiểu dữ liệu số, chứa các số nguyên lớn hơn kiểu
number
. - Undefined: Giá trị của biến chưa được khởi tạo hoặc hàm không trả về gì.
- Null: Giá trị null, thể hiện biến không có giá trị.
- Symbol: Một giá trị duy nhất và bất biến, thường được sử dụng làm khóa trong đối tượng.
// Kiểu dữ liệu string
let myString: string = "Hello, TypeScript!";
// Kiểu dữ liệu number
let myNumber: number = 42;
// Kiểu dữ liệu boolean
let myBoolean: boolean = true;
// Kiểu dữ liệu bigint (phải có hậu tố 'n' cho các số lớn)
let myBigInt: bigint = 9007199254740991n;
// Kiểu dữ liệu undefined (thường sử dụng cho biến chưa được khởi tạo giá trị)
let myUndefined: undefined = undefined;
// Kiểu dữ liệu null
let myNull: null = null;
// Kiểu dữ liệu symbol (sử dụng cho các giá trị duy nhất)
let mySymbol: symbol = Symbol("uniqueIdentifier");
// Hiển thị các giá trị trên console
console.log("String:", myString);
console.log("Number:", myNumber);
console.log("Boolean:", myBoolean);
console.log("BigInt:", myBigInt);
console.log("Undefined:", myUndefined);
console.log("Null:", myNull);
console.log("Symbol:", mySymbol);
Đối với các giá trị nguyên thuỷ trong TypeScript, chúng không thể bị thay đổi (bất biến). Điều này có thể hiểu là những giá trị nguyên thuỷ đã được tạo ra sẽ không thể thay đổi.
Việc này sẽ dễ gây nhầm lẫn với việc gán giá trị nguyên thuỷ cho một biến. Trong trường hợp này, bạn hoàn toàn có thể thay đổi giá trị của biến bằng cách gán cho nó một giá trị khác. Điều đó đơn giản là bạn đang trỏ biến đó tới một giá trị khác chứ không phải là bạn thay đổi giá trị nguyên thuỷ lúc đầu.
let blog = 'TypeScript dễ mà'
blog[0] = 'Sao mà đổi được' //ERROR: Cannot assign to read only property '0' of string 'TypeScript dễ mà'
blog = 'Cái này đổi ngon luôn nè'
console.log(blog) // [LOG]: "Cái này đổi ngon luôn nè"
let arr = [1,5,2,0,0,2]
arr[0] = [4,4,1]
arr[1] = 200
console.log(arr) // [LOG]: [[4, 4, 1], 200, 2, 0, 0, 2]
Ví dụ trên cũng đã phần nào cho bạn hình dung về tính chất bất biến của một giá trị nguyên thủy. Bên cạnh đó bạn có thể thấy được, sự khác biệt khi thay đổi giá trị của một kiểu dữ liệu nguyên thuỷ, so với một kiểu dữ liệu tham chiếu.Trong khi giá trị nguyên thuỷ không thể thay đổi thì kiểu dữ liệu thì có thể vô tư làm được việc đó.
Để tìm hiểu lý do tại sao có sự khác biệt đó thì hãy cùng 200Lab đi tiếp phần tiếp theo nhé. Ngoài những điều để đề cập ở trên, TypeScript có thể chú thích kiểu cho biến (type annotation" hoặc "type signature”).
let id: number = 200;
let firstname: string = '200Lab';
let hasDog: boolean = true;
let unit: number; // Declare variable without assigning a value
unit = 5;
Nhưng trên thực tế không cần phải chú thích cụ thể kiểu dữ liệu cho biến như trên vì TypeScript sẽ tự động hiểu kiểu dữ liệu.
2.2 Reference Type (Kiểu tham chiếu)
Reference Types là một khái niệm trong JavaScript và TypeScript để chỉ các kiểu dữ liệu mà giá trị của chúng được lưu trữ ở một địa chỉ trong bộ nhớ (memory).
Khi bạn làm việc với các kiểu tham chiếu, bạn không thực sự làm việc trực tiếp với giá trị của dữ liệu, mà thay vào đó là với tham chiếu (địa chỉ) đến nơi dữ liệu đó được lưu trữ.
2.2.1 Primitive types vs Reference Types
- Primitive Types: Lưu trữ trực tiếp giá trị trong biến. Khi bạn gán một giá trị nguyên thủy từ biến này sang biến khác, một bản sao của giá trị được tạo ra.
- Reference Types: Lưu trữ địa chỉ của giá trị trong biến. Khi bạn gán một biến kiểu tham chiếu cho biến khác, cả hai biến đều trỏ đến cùng một giá trị trong bộ nhớ.
Nếu ví dụ ở phần trên chưa thực sự cho các bạn một câu trả lời rõ ràng nhất thì mình có một đoạn code cụ thể hơn như sau:
let x = 1;
let y = 5;
x = y;
y = 2;
console.log(x); // 5 (Mặc dù y đã được gán giá trị mới bằng 2, nhưng x vẫn là 5)
let x = { a: 1, b: 1 }; // khai báo x là object chứa a,b
let y = x;
x.b = 100;
console.log(y.b); // 100 (x và y được tham chiếu tới cùng một địa chỉ trên bộ nhớ - Nơi lưu trữ value của của object
2.2.2 Array (Mảng)
Array là một danh sách có thứ tự chứa nhiều giá trị. Mỗi giá trị có một chỉ số (index) duy nhất. Trong TypeScript có thể xác định được kiểu dữ liệu của phần tử bất kì.
let ids: number[] = [1, 2, 3, 4, 5]; // can only contain numbers
let names: string[] = ['Thanh', 'Vy', 'Viet']; // can only contain strings
let options: boolean[] = [true, false, false]; can only contain true or false
let blogs: object[] = [
{ name: 'Syntax TypeScript cơ bản', author: 'Tran Vy' },
{ name: 'TypeScript là gì', author: 'Viet Tran' },
]; // can only contain objects
let arr: any[] = ['hello', 1, true]; // any basically reverts TypeScript back into JavaScript
ids.push(6);
ids.push('7'); // ERROR: Argument of type 'string' is not assignable to parameter of type 'number'.
Đặt biệt, TypeScript có một loại mảng đặt biệt có tên Tuples. Đây là một loại mảng mà có thể chỉ định số lượng phần tử và kiểu dữ liệu cụ thể ứng với từng phần từ đó.
let person: [number, string, boolean] = [200, 'Lab', true];
person[0] = '500'; // Error - Value at index 0 can only be a number
Bạn có thể sử dụng kiểu Union
để xác định mảng chứa nhiều kiểu:
let person: (string | number | boolean)[] = ['Danny', 1, true];
person[0] = 100;
person[1] = {name: 'Danny'} // Error - person array can't contain objects
2.2.3 Object (Đối tượng)
Object là một cấu trúc dữ liệu cho phép lưu trữ các cặp properties-value. Properties thường là chuỗi (hoặc symbol
) và value có thể là bất kỳ kiểu dữ liệu nào.
Object dùng để lưu trữ dữ liệu có cấu trúc, Đối tượng có thể chứa các thuộc tính và phương thức. Lưu ý: Trong TypeScript phải xác định kiểu dữ liệu cho từng value của các properties trong object.
// Xác định kiểu dữ liệu cho từng thuộc tính
let company: {
name: string;
location: string;
isProgrammer: boolean;
};
// Khi gán giá trị cho thuộc tính của object ta phải gán giá trị đúng với kiểu dữ liệu của chúng
company = {
name: '200Lab',
location: 'Việt Nam',
isProgrammer: true,
};
company.isProgrammer = 'Đúng'; // ERROR: Type 'string' is not assignable to type 'boolean'.
company = {
name: '200Lab',
location: 'Việt Nam',
};
// ERROR: Property 'isProgrammer' is missing in type '{ name: string; location: string; }' but required in type '{ name: string; location: string; isProgrammer: boolean; }'.
2.2.4 Function (Hàm)
Đối với hàm chúng ta có thể định nghĩa các kiểu đối số của hàm cũng như kiểu trả về của hàm:
//Định nghĩa kiểu dữ liệu truyền vào là 'number' và kiểu trả về là 'string'
function circle(radius: number): string {
return 'The circumference is ' + 2 * Math.PI * radius;
}
console.log(circle(10)); // Chu vi hình tròn: 31.41592653589793
Ngoài cách khởi tạo hàm như trên, trong ES6 chúng ta còn có thể khởi tạo hàm bằng mũi tên '=>':
const circle = (radius: number): string => {
return 'The circumference is ' + 2 * Math.PI * radius;
};
console.log(circle(10)); // Chu vi hình tròn: 31.41592653589793
Trong khi khai báo hàm bằng "=>" như trên, chúng ta không nhất thiết phải khai báo circle
là một hàm và cũng không cần định nghĩa kiểu trả về. TypeScript sẽ hiểu được những điều đó. Tuy nhiên ở những hàm phức tạp và có những ràng buộc nhất định thì nên định nghĩa chính xác kiểu trả về để đảm bảo được sự tường minh của hàm.
Nếu các bạn có thắc mắc về sự khác nhau về 2 kiểu khai báo hàm này thì mình sẽ so sánh ngắn gọn như sau:
Feature | Arrow Function | Regular Function |
---|---|---|
Cú pháp | Sử dụng => , ngắn gọn |
Sử dụng từ khóa function |
Ngữ cảnh this |
Kế thừa this từ ngữ cảnh bên ngoài |
this phụ thuộc vào cách hàm được gọi |
arguments object |
Không có arguments object |
Có arguments object |
Hàm tạo (constructor ) |
Không thể sử dụng làm hàm tạo | Có thể sử dụng làm hàm tạo |
Cú pháp ngắn gọn | Thường được sử dụng cho các hàm ngắn | Cú pháp dài hơn, đặc biệt cho các hàm đơn giản |
Khi nào sử dụng | Giữ ngữ cảnh this cố định, callback ngắn |
Cần sử dụng this với các ngữ cảnh khác nhau, hàm tạo |
Trong trường hợp hàm của bạn không có tham số trả về bạn có thể trả về void. Và tất nhiên bạn cũng có thể không trả về gì cả. Vì khi bạn không định nghĩa cho kiểu trả về thì TypeSciprt cũng sẽ tự động hiểu được điều đó.
// Khai báo hàm với kiểu trả về là void
let sayHello: (name: string) => void;
// Và đây là khi không khai báo kiểu trả về
sayHello = (name) => {
console.log('Hello ' + name);
};
sayHello('đọc giả của 200Lab'); // Hello đọc giả của 200Lab
2.2.5 Classes (Lớp)
TypeScript cung cấp hỗ trợ đầy đủ cho class
từ khóa được giới thiệu trong ES2015. class
là một mô hình để tạo ra các đối tượng có thuộc tính và phương thức giống nhau. Đây là cách viết hiện đại và dễ hiểu hơn của việc tạo đối tượng so với các function constructor. Class giúp bạn tạo ra các đối tượng với các thuộc tính và phương thức chung, đồng thời cung cấp tính năng kế thừa.
Đối với class bạn cũng có thể khai báo một class rỗng như này: class Preson {}
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
greet() {
console.log(`Hello, my name is ${this.name}`);
}
}
let john = new Person('John', 30);
john.greet(); // Output: Hello, my name is John
Với class thông thường thì bạn phải định nghĩa kiểu dữ liệu cho các thuộc tính, trừ khi bạn đã gán giá trị mặc định cho nó ngay từ ban đầu, thì khi đó TypeScript sẽ tự hiểu được kiểu dữ liệu của thuộc tính đó. Ví dụ:
class Person {
name = "200Lab"
age = 3
}
const pagoda = new Person();
console.log(`${pagoda.name}, ${pagoda.age}`); // LOG: 200lab, 3
Tuy nhiên có một trường hợp không cần gán giá trị mặc định cho thuộc tính trong class nhưng cũng không cần hàm khởi tạo. Nếu bạn có ý định khởi tạo chắc chắn một thuộc tính thông qua các phương tiện khác ngoài hàm tạo (ví dụ, có thể một thư viện bên ngoài đang là một phần của class), bạn có thể sử dụng toán tử khẳng định gán mặc định !
:
class OKGreeter {
// Chưa được khởi tạo nhưng không xảy ra lỗi
name!: string;
}
readonly
Các trường (field) có thể được thêm tiền tố bằng readonly
. Điều này ngăn chặn việc thay đổi, bị gán lại bên ngoài hàm dựng (constructor).
class Greeter {
readonly name: string = "world";
constructor(otherName?: string) {
if (otherName !== undefined) {
this.name = otherName;
}
}
err() {
this.name = "not ok";
} //ERROR: Cannot assign to 'name' because it is a read-only property.
}
const g = new Greeter();
g.name = "also not ok";
//ERROR: Cannot assign to 'name' because it is a read-only property.
2.2.7 Map
Map
là một cấu trúc dữ liệu cho phép lưu trữ các cặp key-value, trong đó key có thể là bất kỳ kiểu dữ liệu nào. Tương tự như đối tượng, nhưng Map
cho phép sử dụng các kiểu dữ liệu phức tạp làm key.
let map = new Map();
map.set('name', 'John');
map.set('age', 30);
console.log(map.get('name')); // Output: John
console.log(map.size); // Output: 2
2.2.8 Set
Set
là một cấu trúc dữ liệu lưu trữ các giá trị duy nhất, không cho phép phần tử trùng lặp. Dùng để lưu trữ một tập hợp các giá trị mà không có giá trị nào lặp lại.
let set = new Set();
set.add(1);
set.add(2);
set.add(1); // Giá trị 1 sẽ không được thêm lần nữa vì đã tồn tại
console.log(set.size); // Output: 2
console.log(set.has(1)); // Output: true
2.3 Dynamic (any) types
Sử dụng any
, về cơ bản chúng ta có thể chuyển TypeScript trở lại thành JavaScript:
let age: any = '100';
age = 100;
age = {
years: 100,
months: 2,
};
Bạn nên tránh sử dụng any
kiểu này càng nhiều càng tốt vì nó ngăn TypeScript thực hiện chức năng của mình – và có thể dẫn đến lỗi.
2.4 Type Aliases
Aliases
giảm trùng lặp mã, giữ cho mã của chúng ta DRY. Dưới đây, chúng ta có thể thấy biệt danh PersonObject
kiểu đã ngăn chặn sự lặp lại và hoạt động như một nguồn duy nhất về dữ liệu mà một đối tượng người dùng nên chứa.
type StringOrNumber = string | number;
type PersonObject = {
name: string;
id: StringOrNumber;
};
const person1: PersonObject = {
name: 'John',
id: 1,
};
const person2: PersonObject = {
name: 'Delia',
id: 2,
};
const sayHello = (person: PersonObject) => {
return 'Hi ' + person.name;
};
const sayGoodbye = (person: PersonObject) => {
return 'Seeya ' + person.name;
};
3. Tổng kết
Qua bài viết này, hy vọng bạn cũng đã hiểu rõ sự khác biệt giữa các kiểu dữ liệu trong TypeScript, từ các kiểu dữ liệu nguyên thủy như string
, number
, boolean
, bigint
, undifined
, null
, symbol
cho đến các kiểu dữ liệu tham chiếu như Object
, Array
, Function
, Class
, Map
và Set
.
Việc nắm vững những khái niệm này sẽ giúp bạn tận dụng tối đa sức mạnh của TypeScript trong các dự án, từ đó xây dựng các ứng dụng mạnh mẽ và đáng tin cậy.
Các bài viết liên quan tại Blog 200Lab
Bài viết liên quan
Giới thiệu Kiến trúc Backend for Frontend (BFF)
Nov 16, 2024 • 10 min read
Flask là gì? Hướng dẫn tạo Ứng dụng Web với Flask
Nov 15, 2024 • 7 min read
Webhook là gì? So sánh Webhook và API
Nov 15, 2024 • 8 min read
Spring Boot là gì? Hướng dẫn Khởi tạo Project Spring Boot với Docker
Nov 14, 2024 • 6 min read
Two-Factor Authentication (2FA) là gì? Vì sao chỉ Mật khẩu thôi là chưa đủ?
Nov 13, 2024 • 7 min read
Test-Driven Development (TDD) là gì? Hướng dẫn thực hành TDD
Nov 13, 2024 • 6 min read