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:

  1. Khai báo một biến age kiểu số (number) và gán giá trị.
  2. Khai báo một biến studentName kiểu chuỗi (string) và gán giá trị.
  3. Khai báo một biến isLearning kiểu boolean và gán giá trị.
  4. 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ị.
  5. 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ặc string[]). Cú pháp Array<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:

  1. Định nghĩa một interface có tên Book 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)
  2. Tạo một biến myBook kiểu Book và gán giá trị cho nó.
  3. Định nghĩa một type alias có tên Person 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ính street (chuỗi) và city (chuỗi))
  4. Tạo một biến authorInfo kiểu Person 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ểu Book.
  • 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 trong Person là kiểu Address đã tạo.
  • TypeScript sẽ kiểm tra xem đối tượng bạn gán cho biến có kiểu Book hoặc Person 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:

  1. 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ểu void). Hàm này chỉ in ra lời chào "Xin chào, [name]!".
  2. Viết một hàm tên calculateArea nhận vào hai tham số widthheight đều là kiểu số và trả về diện tích (kiểu số).
  3. Viết một hàm tên getUserStatus nhận vào một user kiểu Person (đã đị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:

  1. 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ó.
  2. Định nghĩa một type alias tên LogLevel chỉ có thể nhận một trong các giá trị chuỗi cố định: "info", "warn", "error", "debug".
  3. Khai báo một biến currentLogLevel kiểu LogLevel 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ến flexibleId 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ểu LogLevel đượ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ố địnhkiểu dữ liệu khác nhau cho từng vị trí trong mảng.

Yêu cầu:

  1. Đị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ểu number) từ 0 đến 255. Tạo một biến kiểu rgbColor và gán giá trị.
  2. Đị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ểu userInfoTuple 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 anyunknown (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:

  1. Khai báo một biến data kiểu any. 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.
  2. Khai báo một biến uncertainValue kiểu unknown. 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.
  3. Sử dụng Type Narrowing (thu hẹp kiểu) với typeof để kiểm tra kiểu của uncertainValue 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ểu unknown, bạn phải kiểm tra kiểu của nó (ví dụ: dùng typeof, 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:

  1. Định nghĩa hai interface: Circle với thuộc tính kind là literal "circle" và thuộc tính radius (số). Định nghĩa Square với thuộc tính kind là literal "square" và thuộc tính sideLength (số).
  2. Định nghĩa một type alias Shape là Union của CircleSquare (Circle | Square).
  3. Viết một hàm getShapeArea nhận vào một tham số shape kiểu Shape và trả về diện tích (số). Sử dụng thuộc tính kind để 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ến shape phải là kiểu Circle (hoặc Square trong khối else).
  • Đ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:

  1. Định nghĩa một interface HasId với thuộc tính id (số).
  2. Định nghĩa một interface Timestamped với thuộc tính createdAt (kiểu Date) và updatedAt (kiểu Date, là tùy chọn).
  3. Định nghĩa một interface SoftDeletable với thuộc tính deletedAt (kiểu Date hoặc null, tùy chọn).
  4. Định nghĩa một type alias UserRecord là Intersection của HasId, Person (từ Bài 2), Timestamped, và SoftDeletable.
  5. Tạo một biến fullUserProfile kiểu UserRecord 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ính id (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

There are no comments at the moment.