Bài 11.5: Bài tập thực hành hệ thống type trong TypeScript

Bài 11.5: Bài tập thực hành hệ thống type trong TypeScript
Chào mừng bạn trở lại với hành trình chinh phục lập trình Web Front-end!
Chúng ta đã cùng nhau đi qua lý thuyết về hệ thống type trong TypeScript – hiểu được nó là gì, các kiểu dữ liệu cơ bản, cách khai báo kiểu cho biến, hàm, đối tượng và nhiều hơn nữa. Lý thuyết rất quan trọng, nhưng để thực sự thấm và sử dụng TypeScript một cách hiệu quả, thực hành là điều không thể thiếu.
Bài viết này sẽ không tập trung vào việc giải thích lại lý thuyết chi tiết, mà thay vào đó, chúng ta sẽ cùng nhau làm các bài tập, từ cơ bản đến nâng cao một chút, để củng cố kiến thức và làm quen với việc áp dụng các kiểu dữ liệu trong thực tế. Hãy mở trình soạn thảo code và bắt tay vào thực hành ngay nhé!
Tại Sao Phải Thực Hành Hệ Thống Type?
- Bắt lỗi sớm: Hệ thống type giúp bạn phát hiện ra phần lớn lỗi liên quan đến kiểu dữ liệu ngay từ lúc biên dịch, thay vì để chúng ẩn mình và gây lỗi runtime khó chịu.
- Mã nguồn dễ đọc và hiểu hơn: Khi code có type annotations rõ ràng, bạn (và đồng đội của bạn) sẽ dễ dàng biết được dữ liệu mong đợi là gì ở mỗi phần của chương trình.
- Hỗ trợ mạnh mẽ từ công cụ (IDE): Các IDE hiện đại có thể cung cấp tính năng tự động hoàn thành code (autocompletion), kiểm tra lỗi trực tiếp (linting), và điều hướng code thông minh nhờ thông tin về kiểu dữ liệu.
Giờ thì, hãy bắt đầu thôi!
Bài Tập 1: Các Kiểu Dữ Liệu Cơ Bản và Mảng
Đây là bài tập khởi động, giúp bạn làm quen lại với việc khai báo biến với các kiểu dữ liệu cơ bản nhất trong TypeScript và cách khai báo mảng.
Yêu cầu:
- Khai báo một biến
age
kiểu số (number) và gán giá trị. - Khai báo một biến
studentName
kiểu chuỗi (string) và gán giá trị. - Khai báo một biến
isLearning
kiểu boolean và gán giá trị. - Khai báo một biến
scores
là một mảng các số (array of numbers) và gán một vài giá trị. - Khai báo một biến
courses
là một mảng các chuỗi (array of strings) và gán một vài giá trị.
Lời giải và Giải thích:
// 1. Biến kiểu số (number)
let age: number = 25;
console.log(`Tuổi: ${age}`); // Output: Tuổi: 25
// 2. Biến kiểu chuỗi (string)
let studentName: string = "Nguyễn Văn A";
console.log(`Tên học sinh: ${studentName}`); // Output: Tên học sinh: Nguyễn Văn A
// 3. Biến kiểu boolean
let isLearning: boolean = true;
console.log(`Đang học: ${isLearning}`); // Output: Đang học: true
// 4. Mảng các số (array of numbers)
let scores: number[] = [85, 90, 78, 92];
console.log(`Điểm số: ${scores}`); // Output: Điểm số: 85,90,78,92
// Hoặc sử dụng cú pháp Generic
let anotherScores: Array<number> = [70, 88, 95];
console.log(`Điểm số khác: ${anotherScores}`);
// 5. Mảng các chuỗi (array of strings)
let courses: string[] = ["HTML", "CSS", "JavaScript", "TypeScript"];
console.log(`Các khóa học: ${courses}`); // Output: Các khóa học: HTML,CSS,JavaScript,TypeScript
// Hoặc sử dụng cú pháp Generic
let anotherCourses: Array<string> = ["React", "Next.js"];
console.log(`Các khóa học khác: ${anotherCourses}`);
Giải thích:
- Chúng ta sử dụng cú pháp
tên_biến: kieu_du_lieu = gia_tri;
để khai báo biến với kiểu dữ liệu rõ ràng. - Đối với mảng, cú pháp phổ biến là
kieu_phan_tu[]
(ví dụ:number[]
hoặcstring[]
). Cú phápArray<kieu_phan_tu>
cũng cho kết quả tương tự. - Nếu bạn cố gắng gán một giá trị sai kiểu (ví dụ: gán chuỗi cho biến
age
), TypeScript compiler sẽ báo lỗi ngay lập tức. Đây chính là sức mạnh của hệ thống type!
Bài Tập 2: Kiểu Đối Tượng (Interfaces & Type Aliases)
Đối tượng là cấu trúc dữ liệu rất phổ biến trong JavaScript và TypeScript. Việc định nghĩa rõ ràng cấu trúc của đối tượng giúp code của bạn an toàn và dễ quản lý hơn nhiều.
Yêu cầu:
- Định nghĩa một
interface
có tênBook
với các thuộc tính sau:title
(chuỗi)author
(chuỗi)yearPublished
(số)isAvailable
(boolean)isbn
(chuỗi, là thuộc tính tùy chọn)
- Tạo một biến
myBook
kiểuBook
và gán giá trị cho nó. - Định nghĩa một
type alias
có tênPerson
với các thuộc tính sau:name
(chuỗi)age
(số)address
(đối tượng, với các thuộc tínhstreet
(chuỗi) vàcity
(chuỗi))
- Tạo một biến
authorInfo
kiểuPerson
và gán giá trị cho nó.
Lời giải và Giải thích:
// 1 & 2. Sử dụng Interface cho Book
interface Book {
title: string;
author: string;
yearPublished: number;
isAvailable: boolean;
isbn?: string; // Thuộc tính tùy chọn
}
const myBook: Book = {
title: "Clean Code",
author: "Robert C. Martin",
yearPublished: 2008,
isAvailable: true,
// isbn là tùy chọn, nên có thể có hoặc không
};
const anotherBook: Book = {
title: "The Pragmatic Programmer",
author: "Andrew Hunt, David Thomas",
yearPublished: 1999,
isAvailable: false,
isbn: "978-0201616224"
};
console.log(`Thông tin sách 1:`, myBook);
console.log(`Thông tin sách 2:`, anotherBook);
// 3 & 4. Sử dụng Type Alias cho Person
type Address = {
street: string;
city: string;
};
type Person = {
name: string;
age: number;
address: Address; // Sử dụng kiểu Address đã định nghĩa
};
const authorInfo: Person = {
name: "Jane Doe",
age: 40,
address: {
street: "123 Main St",
city: "Anytown"
}
};
console.log(`Thông tin tác giả:`, authorInfo);
Giải thích:
interface Book
định nghĩa "khuôn mẫu" cho bất kỳ đối tượng nào được coi làBook
. Mỗi thuộc tính có tên và kiểu dữ liệu riêng.- Dấu
?
sau tên thuộc tính (isbn?
) chỉ định đây là thuộc tính tùy chọn (optional property). Bạn có thể bỏ qua nó khi tạo đối tượng kiểuBook
. type Person = { ... }
cũng làm điều tương tự nhưinterface
, định nghĩa cấu trúc của đối tượng.type
linh hoạt hơn một chút và có thể được sử dụng cho union, intersection, literal types,...- Chúng ta có thể lồng các kiểu lại với nhau, như cách định nghĩa
address
trongPerson
là kiểuAddress
đã tạo. - TypeScript sẽ kiểm tra xem đối tượng bạn gán cho biến có kiểu
Book
hoặcPerson
có khớp với định nghĩa hay không (có đủ thuộc tính bắt buộc không, các thuộc tính có đúng kiểu không).
Bài Tập 3: Kiểu Hàm (Function Types)
Hàm là trái tim của mọi chương trình. Định nghĩa rõ ràng kiểu dữ liệu cho tham số đầu vào và giá trị trả về của hàm là cực kỳ quan trọng để hiểu cách sử dụng hàm đó và đảm bảo tính đúng đắn.
Yêu cầu:
- Viết một hàm tên
greet
nhận vào một tham sốname
kiểu chuỗi và không trả về giá trị nào (kiểuvoid
). Hàm này chỉ in ra lời chào "Xin chào, [name]!". - Viết một hàm tên
calculateArea
nhận vào hai tham sốwidth
vàheight
đều là kiểu số và trả về diện tích (kiểu số). - Viết một hàm tên
getUserStatus
nhận vào mộtuser
kiểuPerson
(đã định nghĩa ở Bài 2) và trả về một chuỗi mô tả trạng thái của người dùng.
Lời giải và Giải thích:
// 1. Hàm không trả về giá trị (void)
function greet(name: string): void {
console.log(`Xin chào, ${name}!`);
}
greet("TypeScript User"); // Output: Xin chào, TypeScript User!
// greet(123); // Error! Argument of type 'number' is not assignable to parameter of type 'string'.
// 2. Hàm trả về giá trị số
function calculateArea(width: number, height: number): number {
return width * height;
}
const area = calculateArea(10, 5); // area được suy luận là kiểu number
console.log(`Diện tích: ${area}`); // Output: Diện tích: 50
// const invalidArea = calculateArea("abc", 5); // Error! Argument of type 'string' is not assignable to parameter of type 'number'.
// 3. Hàm nhận đối tượng làm tham số và trả về chuỗi
function getUserStatus(user: Person): string {
// Sử dụng thuộc tính của đối tượng user dựa trên kiểu Person
if (user.age >= 18) {
return `${user.name} (Tuổi: ${user.age}) đến từ ${user.address.city} đã trưởng thành.`;
} else {
return `${user.name} (Tuổi: ${user.age}) đến từ ${user.address.city} chưa trưởng thành.`;
}
}
const userStatus = getUserStatus(authorInfo); // Sử dụng biến authorInfo từ Bài 2
console.log(userStatus); // Output: Jane Doe (Tuổi: 40) đến từ Anytown đã trưởng thành.
const youngUser: Person = {
name: "Peter Pan",
age: 10,
address: { street: "Neverland St", city: "Dreamcity" }
};
console.log(getUserStatus(youngUser)); // Output: Peter Pan (Tuổi: 10) đến từ Dreamcity chưa trưởng thành.
Giải thích:
- Chúng ta khai báo kiểu cho từng tham số trong dấu ngoặc đơn của hàm (ví dụ:
name: string
,width: number
,height: number
). - Kiểu dữ liệu trả về của hàm được đặt sau dấu đóng ngoặc đơn và dấu hai chấm (ví dụ:
): void
,): number
,): string
). - Nếu hàm không trả về giá trị nào, chúng ta sử dụng kiểu
void
. - TypeScript sẽ kiểm tra xem bạn có truyền đúng số lượng tham số và đúng kiểu dữ liệu khi gọi hàm hay không. Nó cũng đảm bảo giá trị bạn
return
từ hàm có đúng kiểu đã khai báo hay không.
Bài Tập 4: Kiểu Union và Literal
Kiểu Union (|
) cho phép một biến có thể nhận một trong nhiều kiểu dữ liệu khác nhau. Kiểu Literal cho phép một biến chỉ nhận các giá trị cụ thể, cố định. Kết hợp chúng tạo ra Literal Union, rất mạnh mẽ cho việc định nghĩa các trạng thái hoặc giá trị cố định.
Yêu cầu:
- Khai báo một biến
flexibleId
có thể là kiểu số hoặc kiểu chuỗi. Gán lần lượt một giá trị số và một giá trị chuỗi cho nó. - Định nghĩa một
type alias
tênLogLevel
chỉ có thể nhận một trong các giá trị chuỗi cố định:"info"
,"warn"
,"error"
,"debug"
. - Khai báo một biến
currentLogLevel
kiểuLogLevel
và gán một giá trị hợp lệ. Thử gán một giá trị không hợp lệ và xem lỗi.
Lời giải và Giải thích:
// 1. Kiểu Union (number | string)
let flexibleId: number | string;
flexibleId = 101; // Gán kiểu number, OK
console.log(`Flexible ID (number): ${flexibleId}`);
flexibleId = "user-xyz-789"; // Gán kiểu string, OK
console.log(`Flexible ID (string): ${flexibleId}`);
// flexibleId = true; // Error! Type 'boolean' is not assignable to type 'string | number'.
// 2 & 3. Kiểu Literal Union
type LogLevel = "info" | "warn" | "error" | "debug";
let currentLogLevel: LogLevel = "info"; // Gán giá trị hợp lệ
console.log(`Current Log Level: ${currentLogLevel}`);
currentLogLevel = "warn"; // Gán giá trị hợp lệ khác, OK
console.log(`Current Log Level: ${currentLogLevel}`);
// currentLogLevel = "verbose"; // Error! Type '"verbose"' is not assignable to type 'LogLevel'.
Giải thích:
- Dấu
|
được sử dụng để kết hợp các kiểu, tạo thành Union Type (number | string
). BiếnflexibleId
có thể là số hoặc chuỗi, nhưng không thể là bất kỳ kiểu nào khác. type LogLevel = "info" | "warn" | "error" | "debug";
là một ví dụ về Literal Union. Nó định nghĩa một tập hợp các giá trị chuỗi cụ thể mà biến kiểuLogLevel
được phép nhận.- Khi bạn cố gắng gán một giá trị không nằm trong tập hợp Literal Union (ví dụ:
"verbose"
), TypeScript sẽ báo lỗi. Điều này giúp bạn tránh được các lỗi gõ sai tên trạng thái hoặc giá trị.
Bài Tập 5: Kiểu Mảng Tuple
Tuple là một kiểu mảng đặc biệt, cho phép bạn định nghĩa một mảng với số lượng phần tử cố định và kiểu dữ liệu khác nhau cho từng vị trí trong mảng.
Yêu cầu:
- Định nghĩa một tuple tên
rgbColor
biểu diễn màu sắc theo hệ RGB, với 3 phần tử đều là số (kiểunumber
) từ 0 đến 255. Tạo một biến kiểurgbColor
và gán giá trị. - Định nghĩa một tuple tên
userInfoTuple
biểu diễn thông tin người dùng theo thứ tự: ID (số), tên (chuỗi), trạng thái kích hoạt (boolean). Tạo một biến kiểuuserInfoTuple
và gán giá trị. Truy cập và in ra từng phần tử của tuple.
Lời giải và Giải thích:
// 1. Tuple biểu diễn màu RGB
type RGB = [number, number, number]; // Tuple với 3 phần tử kiểu number
const primaryColor: RGB = [255, 0, 0]; // Màu đỏ
// const invalidColor: RGB = [255, 0, 0, 1]; // Error! Source has 4 elements, tuple type has 3.
// const wrongTypeColor: RGB = [255, 0, "blue"]; // Error! Type 'string' is not assignable to type 'number'.
console.log(`Màu RGB: ${primaryColor}`); // Output: Màu RGB: 255,0,0
// 2. Tuple biểu diễn thông tin người dùng
type UserData = [number, string, boolean]; // Tuple với 3 phần tử: number, string, boolean theo thứ tự
const userProfile: UserData = [123, "Alice Wonderland", true];
// const incompleteProfile: UserData = [456, "Bob"]; // Error! Source has 2 elements, tuple type has 3.
console.log(`User ID: ${userProfile[0]}`); // Output: User ID: 123
console.log(`User Name: ${userProfile[1]}`); // Output: User Name: Alice Wonderland
console.log(`Is Active: ${userProfile[2]}`); // Output: Is Active: true
// userProfile[1] = 789; // Error! Type 'number' is not assignable to type 'string'.
Giải thích:
- Tuple được khai báo bằng cách đặt các kiểu dữ liệu của từng phần tử vào trong dấu ngoặc vuông
[]
, theo đúng thứ tự mong muốn (ví dụ:[number, number, number]
hoặc[number, string, boolean]
). - Điểm khác biệt chính giữa tuple và mảng thông thường là số lượng phần tử và kiểu dữ liệu tại mỗi vị trí của tuple là cố định (trừ khi sử dụng Rest Elements, là một chủ đề nâng cao hơn).
- Khi bạn tạo một biến kiểu tuple, bạn phải cung cấp đúng số lượng phần tử và kiểu dữ liệu cho từng vị trí theo định nghĩa của tuple.
- Bạn có thể truy cập từng phần tử của tuple bằng cách sử dụng index (ví dụ:
userProfile[0]
). TypeScript biết kiểu dữ liệu của từng phần tử tại index đó.
Bài Tập 6: Sử dụng any
và unknown
(và sự khác biệt)
any
là kiểu "thả lỏng" nhất trong TypeScript, cho phép bạn gán và truy cập bất kỳ thuộc tính hoặc gọi bất kỳ phương thức nào mà không có kiểm tra kiểu. unknown
là một thay thế an toàn hơn, đòi hỏi bạn phải kiểm tra kiểu trước khi sử dụng biến đó.
Yêu cầu:
- Khai báo một biến
data
kiểuany
. Gán lần lượt một số, một chuỗi, và một đối tượng cho nó. Thử gọi một phương thức bất kỳ trên biến này mà không cần kiểm tra kiểu. - Khai báo một biến
uncertainValue
kiểuunknown
. Thử gọi một phương thức trên biến này (ví dụ:.toUpperCase()
hoặc.toFixed()
) mà không có kiểm tra kiểu và xem lỗi. - Sử dụng Type Narrowing (thu hẹp kiểu) với
typeof
để kiểm tra kiểu củauncertainValue
và sau đó mới gọi phương thức tương ứng.
Lời giải và Giải thích:
// 1. Sử dụng 'any' (thận trọng!)
let data: any = 10; // data là number
console.log(`Any data (number): ${data}`);
data = "hello world"; // data là string
console.log(`Any data (string): ${data}`);
data = { id: 1, name: "Object Data" }; // data là object
console.log(`Any data (object):`, data);
// Với 'any', TypeScript không kiểm tra kiểu khi bạn truy cập thuộc tính/phương thức
// data.someMethodThatDoesNotExist(); // KHÔNG báo lỗi compile-time, nhưng sẽ lỗi runtime!
let result: number = data.id; // OK về mặt type checking, dù data có thể không có id
console.log(`Accessing property 'id' from any data: ${result}`); // Output: Accessing property 'id' from any data: 1
// 2. Sử dụng 'unknown' (an toàn hơn)
let uncertainValue: unknown = "Đây là một chuỗi";
// console.log(uncertainValue.toUpperCase()); // Error! Object is of type 'unknown'.
let anotherUncertainValue: unknown = 42;
// console.log(anotherUncertainValue.toFixed(2)); // Error! Object is of type 'unknown'.
// 3. Sử dụng Type Narrowing với 'unknown'
let finalValue: unknown = Math.random() > 0.5 ? "Một nửa" : 12345;
if (typeof finalValue === 'string') {
// Bên trong khối if này, finalValue được TypeScript hiểu là kiểu string
console.log(`Giá trị là chuỗi (in hoa): ${finalValue.toUpperCase()}`); // OK
} else if (typeof finalValue === 'number') {
// Bên trong khối else if này, finalValue được TypeScript hiểu là kiểu number
console.log(`Giá trị là số (toFixed): ${finalValue.toFixed(2)}`); // OK
}
Giải thích:
any
gần giống với JavaScript thuần: bạn có thể gán bất cứ thứ gì cho nó và truy cập bất cứ thứ gì từ nó mà không có sự can thiệp của TypeScript. Điều này hữu ích trong quá trình migrate code JavaScript sang TypeScript hoặc khi làm việc với các thư viện không có type definition, nhưng nó làm mất đi lợi ích chính của TypeScript.unknown
an toàn hơn nhiều. Khi một biến có kiểuunknown
, bạn phải kiểm tra kiểu của nó (ví dụ: dùngtypeof
,instanceof
, hoặc kiểm tra thuộc tính cụ thể) trước khi có thể thực hiện các thao tác cụ thể với kiểu đó. Điều này buộc bạn phải xử lý các trường hợp khác nhau của dữ liệu.- Rule of Thumb: Hãy ưu tiên sử dụng
unknown
thay vìany
khi bạn không chắc chắn về kiểu dữ liệu đầu vào, vì nó đảm bảo bạn sẽ phải thực hiện kiểm tra an toàn trước khi sử dụng.
Bài Tập 7: Type Narrowing Nâng Cao (Union với Object/Interface)
Type Narrowing không chỉ dừng lại ở typeof
. Khi làm việc với Union Type bao gồm các đối tượng hoặc interface, bạn có thể sử dụng các thuộc tính đặc trưng (discriminating properties) để thu hẹp kiểu một cách hiệu quả.
Yêu cầu:
- Định nghĩa hai interface:
Circle
với thuộc tínhkind
là literal"circle"
và thuộc tínhradius
(số). Định nghĩaSquare
với thuộc tínhkind
là literal"square"
và thuộc tínhsideLength
(số). - Định nghĩa một
type alias
Shape
là Union củaCircle
vàSquare
(Circle | Square
). - Viết một hàm
getShapeArea
nhận vào một tham sốshape
kiểuShape
và trả về diện tích (số). Sử dụng thuộc tínhkind
để phân biệt giữa hình tròn và hình vuông và tính diện tích tương ứng.
Lời giải và Giải thích:
// 1. Định nghĩa các Interface với thuộc tính phân biệt (discriminating property)
interface Circle {
kind: "circle"; // Literal type để phân biệt
radius: number;
}
interface Square {
kind: "square"; // Literal type để phân biệt
sideLength: number;
}
// 2. Định nghĩa Union Type
type Shape = Circle | Square;
// 3. Hàm sử dụng Type Narrowing dựa trên thuộc tính 'kind'
function getShapeArea(shape: Shape): number {
// Sử dụng thuộc tính 'kind' để thu hẹp kiểu
if (shape.kind === "circle") {
// Bên trong khối if này, shape được TypeScript hiểu là kiểu Circle
return Math.PI * shape.radius ** 2; // OK, thuộc tính 'radius' tồn tại trên Circle
// console.log(shape.sideLength); // Error! Property 'sideLength' does not exist on type 'Circle'.
} else { // implicit check: shape.kind === "square"
// Bên trong khối else này, shape được TypeScript hiểu là kiểu Square
return shape.sideLength ** 2; // OK, thuộc tính 'sideLength' tồn tại trên Square
// console.log(shape.radius); // Error! Property 'radius' does not exist on type 'Square'.
}
}
const myCircle: Circle = { kind: "circle", radius: 10 };
const mySquare: Square = { kind: "square", sideLength: 8 };
console.log(`Diện tích hình tròn: ${getShapeArea(myCircle)}`); // Output: Diện tích hình tròn: 314.159...
console.log(`Diện tích hình vuông: ${getShapeArea(mySquare)}`); // Output: Diện tích hình vuông: 64
Giải thích:
- Chúng ta sử dụng thuộc tính
kind
với kiểu Literal ("circle"
hoặc"square"
) trong mỗi interface. Đây gọi là thuộc tính phân biệt (discriminating property). - Khi TypeScript thấy bạn kiểm tra giá trị của thuộc tính
kind
(ví dụ:shape.kind === "circle"
), nó đủ thông minh để nhận ra rằng bên trong khối điều kiện đó, biếnshape
phải là kiểuCircle
(hoặcSquare
trong khốielse
). - Điều này cho phép bạn truy cập an toàn các thuộc tính riêng của từng kiểu trong Union mà không cần ép kiểu (type assertion), giúp code rõ ràng và an toàn hơn. Đây là một kỹ thuật rất mạnh mẽ khi làm việc với các cấu trúc dữ liệu phức tạp có thể có nhiều dạng khác nhau.
Bài Tập 8: Kiểu Intersection (&
)
Kiểu Intersection (&
) cho phép bạn kết hợp nhiều kiểu lại với nhau thành một kiểu mới chứa tất cả các thuộc tính của các kiểu ban đầu.
Yêu cầu:
- Định nghĩa một interface
HasId
với thuộc tínhid
(số). - Định nghĩa một interface
Timestamped
với thuộc tínhcreatedAt
(kiểuDate
) vàupdatedAt
(kiểuDate
, là tùy chọn). - Định nghĩa một interface
SoftDeletable
với thuộc tínhdeletedAt
(kiểuDate
hoặcnull
, tùy chọn). - Định nghĩa một
type alias
UserRecord
là Intersection củaHasId
,Person
(từ Bài 2),Timestamped
, vàSoftDeletable
. - Tạo một biến
fullUserProfile
kiểuUserRecord
và gán giá trị, bao gồm các thuộc tính từ tất cả các interface đã kết hợp.
Lời giải và Giải thích:
// 1. Interface HasId
interface HasId {
id: number;
}
// 2. Interface Timestamped
interface Timestamped {
createdAt: Date;
updatedAt?: Date; // Tùy chọn
}
// 3. Interface SoftDeletable
interface SoftDeletable {
deletedAt?: Date | null; // Tùy chọn, có thể là Date hoặc null
}
// 4. Type Alias sử dụng Intersection
// Kết hợp HasId, Person (từ Bài 2), Timestamped, và SoftDeletable
type UserRecord = HasId & Person & Timestamped & SoftDeletable;
// 5. Tạo biến kiểu UserRecord
const fullUserProfile: UserRecord = {
id: 1001,
name: "Chris Evans",
age: 42,
address: {
street: "456 Highland Rd",
city: "Villagetown"
},
createdAt: new Date(),
updatedAt: new Date(), // Có thể có hoặc không
deletedAt: null // Có thể là Date, null, hoặc không có
};
console.log("Full User Profile:", fullUserProfile);
const anotherFullUserProfile: UserRecord = {
id: 1002,
name: "Ana De Armas",
age: 35,
address: {
street: "789 Ocean Ave",
city: "Coastcity"
},
createdAt: new Date("2023-01-15T10:00:00Z"),
// Không có updatedAt và deletedAt là hợp lệ vì chúng tùy chọn
};
console.log("Another Full User Profile:", anotherFullUserProfile);
Giải thích:
- Dấu
&
được sử dụng để kết hợp (giao nhau) các kiểu. Kết quả là một kiểu mới bao gồm tất cả các thuộc tính từ các kiểu ban đầu. - Trong ví dụ này,
UserRecord
phải có các thuộc tínhid
(từHasId
),name
,age
,address
(từPerson
),createdAt
,updatedAt
(từTimestamped
), vàdeletedAt
(từSoftDeletable
). - Intersection rất hữu ích khi bạn muốn tạo ra các kiểu dữ liệu phức tạp bằng cách kết hợp các "đặc điểm" (được biểu diễn bởi các interface hoặc type alias) lại với nhau một cách linh hoạt.
Comments