100 câu hỏi phỏng vấn typescript kèm lời giải chi tiết từ A-Z


Table of Contents

1. TypeScript là gì? Tại sao chúng ta nên sử dụng TypeScript thay vì JavaScript?

Câu trả lời: TypeScript là một ngôn ngữ lập trình dựa trên JavaScript, được Microsoft phát triển. Nó bổ sung các tính năng như kiểu tĩnh (static typing), interface, và hỗ trợ mạnh mẽ hơn khi làm việc với các dự án lớn.

Giải thích:

  • TypeScript giúp kiểm tra lỗi tại thời điểm biên dịch, giảm lỗi runtime.
  • TypeScript hỗ trợ các IDE tốt hơn, cung cấp tính năng như gợi ý code (intellisense) và tự động hoàn thành.
  • Nó giúp tổ chức mã tốt hơn với các tính năng như interface, namespace, và module.

2. TypeScript có bắt buộc kiểu tĩnh cho mọi biến không?

Ví dụ:

let a: number = 10; // Được khai báo kiểu number
let b = "Hello";    // Kiểu được suy luận là string
let c;              // Kiểu là `any`

Phân tích:

  • let a: number = 10;: Cú pháp này định nghĩa rõ ràng biến a có kiểu number. Nếu gán giá trị khác kiểu số, TypeScript sẽ báo lỗi.
  • let b = "Hello";: TypeScript tự suy luận kiểu (type inference), xác định b có kiểu string.
  • let c;: Không gán kiểu và giá trị, TypeScript mặc định kiểu là any. Điều này làm mất lợi ích của kiểm tra kiểu, nên cần tránh.

3. Giải thích khái niệm interface trong TypeScript.

Ví dụ:

interface User {
id: number;
name: string;
age?: number; // Thuộc tính tùy chọn
}
const user: User = {
id: 1,
name: "John Doe"
};

Phân tích:

  • interface User: Định nghĩa cấu trúc của đối tượng User, yêu cầu các thuộc tính như id (number), name (string). age là tùy chọn vì có dấu ?.
  • const user: User: Biến user phải tuân theo cấu trúc của User.
  • Nếu bỏ qua id hoặc name, TypeScript sẽ báo lỗi.

4. So sánh interface và type. Khi nào nên sử dụng?

Ví dụ:

interface Point {
x: number;
y: number;
}
type PointAlias = {
x: number;
y: number;
};

Phân tích:

  • Cả interface và type đều dùng để định nghĩa kiểu đối tượng.
  • interface hỗ trợ kế thừa (extends).
  • type linh hoạt hơn, có thể dùng với các kiểu khác như union hoặc intersection:
type Shape = Point & { radius: number }; // Intersection
type StringOrNumber = string | number;  // Union

Khi nào nên dùng:

  • Dùng interface nếu chỉ định nghĩa cấu trúc đối tượng.
  • Dùng type nếu cần tính linh hoạt cao hơn.

5. Generics là gì? Làm thế nào để sử dụng?

Ví dụ:

function identity<T>(arg: T): T {
return arg;
}
const result = identity<number>(42);
const text = identity<string>("Hello");

Phân tích:

  • T là một tham số kiểu (generic parameter), đại diện cho bất kỳ kiểu nào.
  • identity<number>: Gọi hàm với kiểu number, nên arg sẽ là number.
  • identity<string>: Gọi hàm với kiểu string.

6. Phân biệt readonly và const trong TypeScript.

Ví dụ:

const PI = 3.14; // Không thể thay đổi giá trị
interface Circle {
readonly radius: number; // Không thể thay đổi sau khi gán
}
const circle: Circle = { radius: 10 };
// circle.radius = 20; // Lỗi

Phân tích:

  • const: Dùng cho biến, không thể thay đổi tham chiếu sau khi khai báo.
  • readonly: Dùng trong interface/class để định nghĩa thuộc tính không thể thay đổi sau khi được khởi tạo.

7. Giải thích type narrowing và cách sử dụng trong TypeScript.

Ví dụ:

function printValue(value: string | number) {
if (typeof value === "string") {
console.log("String value:", value.toUpperCase());
} else {
console.log("Number value:", value.toFixed(2));
}
}

Phân tích:

  • value: string | number: Kiểu union (có thể là string hoặc number).
  • typeof value === "string": Hẹp phạm vi kiểu (narrowing) để TypeScript biết value là string.
  • Trong mỗi nhánh, IDE sẽ cung cấp gợi ý dựa trên kiểu cụ thể.

8. Giải thích Decorators trong TypeScript.

Ví dụ:

function Log(target: any, propertyKey: string) {
console.log(`Property ${propertyKey} has been accessed`);
}
class Example {
@Log
value: string = "Hello";
}

Phân tích:

  • @Log: Một decorator áp dụng lên thuộc tính.
  • Khi truy cập value, decorator sẽ được thực thi.
  • Decorators hữu ích trong việc ghi log, kiểm tra quyền, hoặc xử lý metadata.

9. Khác biệt giữa unknown và any.

Ví dụ:

let a: any = 10;
let b: unknown = 20;
// Không lỗi
a.toFixed(2);
// Lỗi
// b.toFixed(2);
// Cần kiểm tra kiểu
if (typeof b === "number") {
b.toFixed(2);
}

Phân tích:

  • any: Không có ràng buộc, mất kiểm tra kiểu.
  • unknown: Cần kiểm tra kiểu trước khi sử dụng, an toàn hơn.

10. Mô tả Mapped Types và ứng dụng.

Ví dụ:

type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
interface User {
id: number;
name: string;
}
type ReadonlyUser = Readonly<User>;

Phân tích:

  • [P in keyof T]: Lặp qua các thuộc tính của T.
  • readonly: Gán thuộc tính chỉ đọc.
  • Kết quả là ReadonlyUser có tất cả thuộc tính từ User, nhưng chỉ đọc.

11. Khái niệm Union và Intersection trong TypeScript là gì?

Union:

Cho phép một biến có thể thuộc nhiều kiểu dữ liệu.

Ví dụ:

type StringOrNumber = string | number;
function print(value: StringOrNumber) {
if (typeof value === "string") {
console.log("String value:", value.toUpperCase());
} else {
console.log("Number value:", value.toFixed(2));
}
}

Phân tích:

  • type StringOrNumber = string | number: Đây là kiểu union, cho phép giá trị của value là string hoặc number.
  • Sử dụng typeof để phân biệt kiểu khi xử lý.

Intersection:

Kết hợp nhiều kiểu lại thành một kiểu duy nhất, yêu cầu một giá trị phải thỏa mãn tất cả các kiểu.

Ví dụ:

interface HasName {
name: string;
}
interface HasAge {
age: number;
}
type Person = HasName & HasAge;
const person: Person = {
name: "John",
age: 30,
};

Phân tích:

  • type Person = HasName & HasAge: Đây là kiểu intersection, yêu cầu giá trị phải có cả name và age.
  • person: Đối tượng phải thỏa mãn tất cả các thuộc tính từ HasName và HasAge.

12. Làm thế nào để sử dụng Enums trong TypeScript?

Ví dụ:

enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT",
}
function move(direction: Direction) {
console.log("Moving:", direction);
}
move(Direction.Up);

Phân tích:

  • enum Direction: Định nghĩa các giá trị tĩnh, giúp mã dễ đọc và bảo trì.
  • Direction.Up: Truy cập giá trị của enum.
  • move(Direction.Up): Hàm chỉ nhận giá trị thuộc kiểu Direction.

13. Khái niệm Type Guards là gì?

Ví dụ:

interface Dog {
bark: () => void;
}
interface Cat {
meow: () => void;
}
function isDog(animal: Dog | Cat): animal is Dog {
return (animal as Dog).bark !== undefined;
}
function makeSound(animal: Dog | Cat) {
if (isDog(animal)) {
animal.bark();
} else {
animal.meow();
}
}

Phân tích:

  • isDog: Đây là một type guard, xác định kiểu chính xác của đối tượng.
  • (animal as Dog).bark !== undefined: Kiểm tra nếu animal có phương thức bark.
  • if (isDog(animal)): Dựa trên kết quả của type guard để xử lý.

14. Sử dụng Utility Types trong TypeScript.

TypeScript cung cấp nhiều utility types để xử lý kiểu dễ dàng.

Partial:

Tạo một kiểu trong đó tất cả các thuộc tính đều là tùy chọn.

Ví dụ:

interface User {
id: number;
name: string;
}
type PartialUser = Partial<User>;
const user: PartialUser = { id: 1 };

Pick:

Lấy một tập con các thuộc tính từ kiểu đã cho.

Ví dụ:

type UserName = Pick<User, "name">;
const userName: UserName = { name: "Alice" };

Omit:

Bỏ một hoặc nhiều thuộc tính khỏi kiểu.

Ví dụ:

type UserWithoutId = Omit<User, "id">;
const user: UserWithoutId = { name: "Bob" };

15. Giải thích Optional Chaining trong TypeScript.

Ví dụ:

const user = {
profile: {
name: "Alice",
},
};
console.log(user?.profile?.name); // "Alice"
console.log(user?.address?.city); // undefined

Phân tích:

  • ?.: Kiểm tra xem thuộc tính có tồn tại không trước khi truy cập.
  • user?.address?.city: Nếu address không tồn tại, sẽ trả về undefined thay vì lỗi.

16. Sử dụng Never trong TypeScript.

Never đại diện cho một giá trị không bao giờ xảy ra.

Ví dụ:

function throwError(message: string): never {
throw new Error(message);
}

Phân tích:

  • Hàm throwError không trả về giá trị, do đó kiểu trả về là never.

17. Sử dụng Tuple trong TypeScript.

Ví dụ:

let tuple: [string, number];
tuple = ["John", 25]; // Hợp lệ
// tuple = [25, "John"]; // Lỗi

Phân tích:

  • tuple: [string, number]: Kiểu của tuple xác định thứ tự và kiểu dữ liệu của từng phần tử.
  • Không thể thay đổi thứ tự hoặc kiểu dữ liệu trong tuple.

18. Phân biệt Class và Interface.

Class:

Định nghĩa một kiểu dữ liệu kèm theo logic.

Ví dụ:

class User {
constructor(public name: string, public age: number) {}
greet() {
console.log(`Hello, ${this.name}`);
}
}
const user = new User("Alice", 30);
user.greet();

Interface:

Chỉ định nghĩa cấu trúc dữ liệu, không có logic.

Ví dụ:

interface User {
name: string;
age: number;
}
const user: User = { name: "Alice", age: 30 };

Phân tích:

  • Dùng class khi cần cả dữ liệu và logic.
  • Dùng interface nếu chỉ cần định nghĩa cấu trúc.

19. Phân biệt Abstract Class và Interface.

Abstract Class:

Cho phép định nghĩa một số logic chung và yêu cầu các class con phải triển khai các phương thức cụ thể.

Ví dụ:

abstract class Animal {
abstract sound(): void;
move() {
console.log("Moving...");
}
}
class Dog extends Animal {
sound() {
console.log("Barking...");
}
}
const dog = new Dog();
dog.sound();
dog.move();

Interface:

Chỉ định nghĩa mà không cung cấp logic.

Ví dụ:

interface Animal {
sound(): void;
}
class Cat implements Animal {
sound() {
console.log("Meowing...");
}
}
const cat = new Cat();
cat.sound();

Phân tích:

  • Dùng abstract class khi cần định nghĩa logic chung.
  • Dùng interface khi chỉ cần định nghĩa giao diện.

20. Type Assertion là gì?

Ví dụ:

let someValue: unknown = "Hello World";
let strLength: number = (someValue as string).length;

Phân tích:

  • someValue as string: Ép kiểu someValue thành string.
  • Hữu ích khi TypeScript không thể tự suy luận kiểu chính xác.

21. Sử dụng Record trong TypeScript

Record được sử dụng để tạo kiểu với các khóa và giá trị cụ thể.

Ví dụ:

type Roles = "admin" | "user" | "guest";
type Permissions = Record<Roles, string[]>;
const rolePermissions: Permissions = {
admin: ["read", "write", "delete"],
user: ["read", "write"],
guest: ["read"],
};

Phân tích:

  • Record<Roles, string[]>: Mỗi khóa trong Roles được ánh xạ đến kiểu string[].
  • Hữu ích khi làm việc với các cấu trúc ánh xạ (mapping).

22. Giải thích ReadonlyArray trong TypeScript

Ví dụ:

const arr: ReadonlyArray<number> = [1, 2, 3];
// arr.push(4); // Lỗi
// arr[0] = 10; // Lỗi
const normalArray = arr as number[];
normalArray.push(4); // Hợp lệ vì chuyển đổi sang mảng thông thường

Phân tích:

  • ReadonlyArray: Đảm bảo mảng không thể thay đổi (immutable).
  • Không thể thêm, xóa, hoặc chỉnh sửa giá trị trong mảng.
  • Có thể sử dụng as để chuyển đổi nếu cần thao tác.

23. Khi nào sử dụng keyof trong TypeScript?

keyof dùng để lấy tất cả các khóa của một kiểu đối tượng.

Ví dụ:

interface User {
id: number;
name: string;
}
type UserKeys = keyof User; // "id" | "name"
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user: User = { id: 1, name: "Alice" };
console.log(getProperty(user, "id")); // 1

Phân tích:

  • keyof User: Trích xuất các khóa từ kiểu User, kết quả là "id" | "name".
  • Hàm getProperty: Ràng buộc key phải là một khóa hợp lệ của obj.

24. Sử dụng Indexed Access Types trong TypeScript

Cho phép truy cập kiểu của thuộc tính trong một đối tượng.

Ví dụ:

interface User {
id: number;
name: string;
}
type UserId = User["id"]; // number

Phân tích:

  • User["id"]: Truy cập kiểu của thuộc tính id trong User.
  • Hữu ích khi tái sử dụng kiểu của một thuộc tính cụ thể.

25. Làm thế nào để sử dụng Template Literal Types?

Template Literal Types cho phép kết hợp kiểu với chuỗi.

Ví dụ:

type Action = "read" | "write" | "delete";
type Role = "user" | "admin";
type Permission = `${Role}_${Action}`; // "user_read" | "user_write" | ...
const permission: Permission = "admin_delete"; // Hợp lệ

Phân tích:

  • ${Role}_${Action}: Kết hợp các kiểu Role và Action thành các chuỗi cụ thể.
  • Rất hữu ích khi làm việc với các chuỗi có mẫu định dạng (pattern).

26. Sử dụng Conditional Types trong TypeScript

Cho phép định nghĩa kiểu dựa trên điều kiện.

Ví dụ:

type IsString<T> = T extends string ? true : false;
type Test1 = IsString<string>; // true
type Test2 = IsString<number>; // false

Phân tích:

  • T extends string: Kiểm tra nếu T là string.
  • Kết quả là true hoặc false tùy thuộc vào điều kiện.

27. Giải thích Infer trong TypeScript

Infer được sử dụng trong Conditional Types để suy diễn kiểu.

Ví dụ:

type GetReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type Test = GetReturnType<() => string>; // string

Phân tích:

  • infer R: Suy diễn kiểu trả về của hàm.
  • Nếu T là một hàm, kiểu trả về (R) sẽ được lấy.

28. Sử dụng Namespace trong TypeScript

Ví dụ:

namespace Utils {
export const PI = 3.14;
export function calculateCircumference(radius: number): number {
return 2 * PI * radius;
}
}
console.log(Utils.calculateCircumference(10)); // 62.8

Phân tích:

  • namespace Utils: Tạo không gian tên để tổ chức mã.
  • export: Cho phép sử dụng các thành phần bên ngoài namespace.

29. Phân biệt Module và Namespace

Module:

  • Dựa trên ES Modules (import/export).
  • Mỗi file là một module.
  • Dùng để quản lý mã hiện đại.

Ví dụ:

// math.ts
export const add = (a: number, b: number) => a + b;
// main.ts
import { add } from "./math";
console.log(add(1, 2));

Namespace:

  • Không gian tên nội bộ, không yêu cầu tách file.
  • Ít được sử dụng trong các dự án hiện đại, nhưng vẫn hữu ích cho mã kế thừa.

30. Làm thế nào để khai báo và sử dụng Function Overloads?

Ví dụ:

function getLength(value: string): number;
function getLength(value: any[]): number;
function getLength(value: any): number {
return value.length;
}
console.log(getLength("Hello")); // 5
console.log(getLength([1, 2, 3])); // 3

Phân tích:

  • Function Overloads: Định nghĩa nhiều kiểu cho hàm.
  • getLength(value: string) và getLength(value: any[]): Hai kiểu đầu vào hợp lệ.
  • Phần triển khai (function getLength(value: any): number) xử lý logic chung.

31. Giải thích Declaration Merging trong TypeScript

Cho phép kết hợp nhiều khai báo cùng tên.

Ví dụ:

interface User {
name: string;
}
interface User {
age: number;
}
const user: User = { name: "Alice", age: 30 };

Phân tích:

  • Hai khai báo User được gộp thành một.
  • Đối tượng user phải có cả name và age.

32. Làm thế nào để kiểm soát strictness trong TypeScript?

TypeScript cung cấp cài đặt strictness trong file tsconfig.json.

Ví dụ:

{
"compilerOptions": {
"strict": true
}
}

Các tùy chọn liên quan:

  • noImplicitAny: Báo lỗi nếu TypeScript suy luận kiểu any.
  • strictNullChecks: Không cho phép sử dụng null hoặc undefined mà không kiểm tra.
  • strictPropertyInitialization: Đảm bảo các thuộc tính được khởi tạo trong class.

33. Phân biệt Public, Private, và Protected trong TypeScript

Ví dụ:

class Person {
public name: string;
private age: number;
protected address: string;
constructor(name: string, age: number, address: string) {
this.name = name;
this.age = age;
this.address = address;
}
}
class Employee extends Person {
constructor(name: string, age: number, address: string) {
super(name, age, address);
}
showAddress() {
console.log(this.address); // Hợp lệ
}
}

Phân tích:

  • public: Truy cập ở bất cứ đâu.
  • private: Chỉ truy cập trong class khai báo.
  • protected: Truy cập trong class khai báo và class kế thừa.

34. Làm thế nào để xử lý Module Resolution trong TypeScript?

TypeScript hỗ trợ hai chiến lược:

  • Classic: Phù hợp với dự án không sử dụng module.
  • Node: Sử dụng theo chuẩn Node.js.

Cấu hình trong tsconfig.json:

{
"compilerOptions": {
"moduleResolution": "node"
}
}

35. Sử dụng Type Aliases để định nghĩa kiểu phức tạp

Ví dụ:

type Point = { x: number; y: number };
type Line = { start: Point; end: Point };
const line: Line = {
start: { x: 0, y: 0 },
end: { x: 10, y: 10 },
};

Phân tích:

  • type Point: Định nghĩa một kiểu dữ liệu phức tạp cho tọa độ.
  • type Line: Dùng kiểu Point để tạo kiểu mới cho một đoạn thẳng.
  • Giúp tái sử dụng kiểu và làm mã dễ đọc hơn.

36. Làm thế nào để định nghĩa Union Type trong hàm?

Ví dụ:

function formatValue(value: string | number): string {
if (typeof value === "string") {
return `String: ${value}`;
}
return `Number: ${value}`;
}
console.log(formatValue("Hello")); // String: Hello
console.log(formatValue(42));      // Number: 42

Phân tích:

  • value: string | number: Chấp nhận đầu vào kiểu string hoặc number.
  • Kiểm tra kiểu bằng typeof để xử lý từng trường hợp cụ thể.

37. Làm thế nào để quản lý cấu trúc phức tạp với Nested Types?

Ví dụ:

type Address = {
street: string;
city: string;
};
type User = {
id: number;
name: string;
address: Address;
};
const user: User = {
id: 1,
name: "Alice",
address: {
street: "123 Main St",
city: "Wonderland",
},
};

Phân tích:

  • type Address: Định nghĩa cấu trúc cho địa chỉ.
  • type User: Tích hợp kiểu Address để tạo cấu trúc dữ liệu phức tạp.
  • Dễ dàng mở rộng hoặc tái sử dụng kiểu.

38. Làm thế nào để tạo Dynamic Keys trong TypeScript?

Ví dụ:

type DynamicObject = {
[key: string]: string | number;
};
const data: DynamicObject = {
name: "Alice",
age: 30,
};

Phân tích:

  • [key: string]: string | number: Định nghĩa các khóa động với kiểu dữ liệu cụ thể.
  • Hữu ích khi làm việc với đối tượng không cố định thuộc tính.

39. Làm thế nào để sử dụng This trong TypeScript?

Ví dụ:

class Counter {
count: number = 0;
increment(): this {
this.count++;
return this;
}
decrement(): this {
this.count--;
return this;
}
}
const counter = new Counter();
counter.increment().increment().decrement();
console.log(counter.count); // 1

Phân tích:

  • this: Trả về chính đối tượng hiện tại, cho phép gọi liên tiếp các phương thức.
  • Phù hợp với pattern “method chaining” (chuỗi phương thức).

40. Sử dụng Abstract Class để định nghĩa logic chung

Ví dụ:

abstract class Shape {
abstract getArea(): number;
describe(): string {
return "This is a shape.";
}
}
class Circle extends Shape {
constructor(public radius: number) {
super();
}
getArea(): number {
return Math.PI * this.radius ** 2;
}
}
const circle = new Circle(5);
console.log(circle.getArea()); // 78.53981633974483
console.log(circle.describe()); // This is a shape.

Phân tích:

  • abstract class Shape: Định nghĩa logic chung và các phương thức trừu tượng.
  • getArea: Lớp con (Circle) phải triển khai phương thức này.
  • describe: Phương thức chung có thể dùng trực tiếp mà không cần ghi đè.

41. Sử dụng Non-Nullable Types trong TypeScript

Ví dụ:

type NonNullableString = NonNullable<string | null | undefined>;
const validString: NonNullableString = "Hello";
// const invalidString: NonNullableString = null; // Lỗi

Phân tích:

  • NonNullable: Loại bỏ null và undefined khỏi kiểu.
  • Hữu ích khi muốn đảm bảo giá trị không bao giờ là null hoặc undefined.

42. Làm thế nào để sử dụng Discriminated Union?

Ví dụ:

interface Square {
kind: "square";
size: number;
}
interface Rectangle {
kind: "rectangle";
width: number;
height: number;
}
type Shape = Square | Rectangle;
function calculateArea(shape: Shape): number {
if (shape.kind === "square") {
return shape.size ** 2;
} else if (shape.kind === "rectangle") {
return shape.width * shape.height;
}
throw new Error("Unknown shape");
}
const square: Square = { kind: "square", size: 10 };
console.log(calculateArea(square)); // 100

Phân tích:

  • kind: "square" và kind: "rectangle": Dùng thuộc tính chung để phân biệt kiểu.
  • if (shape.kind === "square"): Xác định kiểu cụ thể dựa trên giá trị của kind.

43. Làm thế nào để sử dụng Optional Modifier (?)?

Ví dụ:

interface User {
id: number;
name?: string; // Thuộc tính tùy chọn
}
const user1: User = { id: 1 };
const user2: User = { id: 2, name: "Alice" };

Phân tích:

  • name?: string: Thuộc tính name là tùy chọn.
  • Giúp linh hoạt trong việc định nghĩa đối tượng.

44. Sử dụng Readonly trong TypeScript

Ví dụ:

interface User {
readonly id: number;
name: string;
}
const user: User = { id: 1, name: "Alice" };
// user.id = 2; // Lỗi
user.name = "Bob"; // Hợp lệ

Phân tích:

  • readonly id: Thuộc tính id không thể thay đổi sau khi khởi tạo.
  • Phần còn lại của đối tượng vẫn có thể chỉnh sửa.

45. Làm thế nào để làm việc với Tuple và Rest Parameters?

Ví dụ:

function log(...args: [string, number]): void {
console.log(`String: ${args[0]}, Number: ${args[1]}`);
}
log("Hello", 42); // String: Hello, Number: 42

Phân tích:

  • ...args: [string, number]: Kết hợp tuple với rest parameters để giới hạn kiểu và số lượng tham số.
  • Đảm bảo log chỉ nhận hai tham số với kiểu cụ thể.

46. Làm thế nào để xử lý kiểu phức tạp với Mapped Types?

Ví dụ:

type Mutable<T> = {
-readonly [P in keyof T]: T[P];
};
interface User {
readonly id: number;
readonly name: string;
}
type EditableUser = Mutable<User>;
const user: EditableUser = { id: 1, name: "Alice" };
user.id = 2; // Hợp lệ

Phân tích:

  • -readonly: Loại bỏ modifier readonly khỏi các thuộc tính.
  • [P in keyof T]: Lặp qua các thuộc tính của kiểu T.

47. Sử dụng Key Remapping in Mapped Types

TypeScript 4.1 trở lên cho phép thay đổi tên thuộc tính trong Mapped Types.

Ví dụ:

type PrefixKeys<T> = {
[K in keyof T as `prefix_${string & K}`]: T[K];
};
interface User {
id: number;
name: string;
}
type PrefixedUser = PrefixKeys<User>;
// Kết quả: { prefix_id: number; prefix_name: string; }
const user: PrefixedUser = {
prefix_id: 1,
prefix_name: "Alice",
};

Phân tích:

  • [K in keyof T as \prefix_${string & K}`]: Thay đổi tên khóa bằng cách thêm tiền tốprefix_`.
  • Hữu ích khi cần tái cấu trúc đối tượng theo định dạng cụ thể.

48. Sử dụng Conditional Types để tạo kiểu linh hoạt

Ví dụ:

type ExtractArrayType<T> = T extends (infer U)[] ? U : never;
type StringArray = string[];
type Result = ExtractArrayType<StringArray>; // string

Phân tích:

  • T extends (infer U)[]: Sử dụng infer để suy diễn kiểu phần tử trong mảng.
  • Kết quả là kiểu phần tử của mảng (ở đây là string).

49. Tích hợp Type Guards và Exhaustive Check

Ví dụ:

type Shape = 
| { kind: "circle"; radius: number }
| { kind: "rectangle"; width: number; height: number };
function getArea(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "rectangle":
return shape.width * shape.height;
default:
const _exhaustiveCheck: never = shape; // Báo lỗi nếu có kiểu mới mà không xử lý
throw new Error(`Unknown shape: ${_exhaustiveCheck}`);
}
}

Phân tích:

  • switch (shape.kind): Sử dụng kind làm type guard.
  • const _exhaustiveCheck: never: Đảm bảo tất cả các trường hợp đều được xử lý. Nếu kiểu mới được thêm vào mà không cập nhật logic, TypeScript sẽ báo lỗi.

50. Làm thế nào để sử dụng String Literal Types trong hàm?

Ví dụ:

function printColor(color: "red" | "green" | "blue"): void {
console.log(`Color: ${color}`);
}
printColor("red");   // Hợp lệ
// printColor("yellow"); // Lỗi

Phân tích:

  • "red" | "green" | "blue": Kiểu chuỗi cụ thể, giới hạn đầu vào.
  • Hữu ích khi muốn giới hạn giá trị cho các tham số hàm.

51. Kế thừa kiểu với Extends trong Generics

Ví dụ:

interface HasId {
id: number;
}
function logWithId<T extends HasId>(item: T): void {
console.log(`ID: ${item.id}`);
}
const user = { id: 1, name: "Alice" };
logWithId(user); // ID: 1

Phân tích:

  • T extends HasId: Đảm bảo T phải có thuộc tính id.
  • logWithId(user): Chỉ hợp lệ nếu đối tượng có kiểu HasId.

52. Xử lý các kiểu phức tạp với Deep Readonly

Ví dụ:

type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};
interface User {
id: number;
profile: {
name: string;
address: {
city: string;
};
};
}
const user: DeepReadonly<User> = {
id: 1,
profile: {
name: "Alice",
address: {
city: "Wonderland",
},
},
};
// user.profile.name = "Bob"; // Lỗi: Thuộc tính chỉ đọc

Phân tích:

  • T[P] extends object ? DeepReadonly<T[P]> : T[P]: Áp dụng readonly đệ quy cho các thuộc tính con.
  • Đảm bảo đối tượng và tất cả thuộc tính con không thể thay đổi.

53. Sử dụng Utility Type: Exclude

Exclude loại bỏ các kiểu cụ thể khỏi kiểu ban đầu.

Ví dụ:

type AllowedTypes = Exclude<string | number | boolean, boolean>;
const value: AllowedTypes = 42; // Hợp lệ
// const value2: AllowedTypes = true; // Lỗi

Phân tích:

  • Exclude<string | number | boolean, boolean>: Loại bỏ boolean khỏi kiểu ban đầu.
  • Kết quả là string | number.

54. Sử dụng Utility Type: Extract

Extract chọn ra các kiểu chung giữa hai kiểu.

Ví dụ:

type CommonTypes = Extract<string | number | boolean, string | boolean>;
const value: CommonTypes = "hello"; // Hợp lệ
// const value2: CommonTypes = 42; // Lỗi

Phân tích:

  • Extract<string | number | boolean, string | boolean>: Chỉ giữ lại các kiểu chung (string | boolean).

55. Làm thế nào để sử dụng Union to Intersection với TypeScript?

Ví dụ:

type UnionToIntersection<U> = (U extends any ? (arg: U) => void : never) extends (arg: infer I) => void ? I : never;
type Result = UnionToIntersection<{ a: string } | { b: number }>;
// Kết quả: { a: string } & { b: number }

Phân tích:

  • UnionToIntersection: Chuyển kiểu union thành intersection.
  • Áp dụng cho các trường hợp cần kết hợp nhiều kiểu thành một kiểu duy nhất.

56. Làm thế nào để kiểm soát Index Signature trong TypeScript?

Ví dụ:

interface Dictionary<T> {
[key: string]: T;
}
const dict: Dictionary<number> = {
a: 1,
b: 2,
};

Phân tích:

  • [key: string]: T: Khóa là chuỗi, giá trị là kiểu T.
  • Hữu ích khi làm việc với các cấu trúc ánh xạ (mapping) linh hoạt.

57. Làm thế nào để tạo kiểu cho các thuộc tính tùy chọn nâng cao?

Ví dụ:

type OptionalProperties<T> = {
[K in keyof T]?: T[K];
};
interface User {
id: number;
name: string;
}
type OptionalUser = OptionalProperties<User>;
const user: OptionalUser = { id: 1 }; // Hợp lệ

Phân tích:

  • [K in keyof T]?: T[K]: Biến tất cả thuộc tính của T thành tùy chọn.

58. Làm thế nào để tạo kiểu Recursive trong TypeScript?

Ví dụ:

type NestedArray<T> = T | NestedArray<T>[];
const array: NestedArray<number> = [1, [2, [3, 4]], 5];

Phân tích:

  • T | NestedArray<T>[]: Cho phép mảng lồng nhau với các phần tử kiểu T.

59. Làm thế nào để sử dụng Asserts trong TypeScript?

Ví dụ:

function assertIsString(value: any): asserts value is string {
if (typeof value !== "string") {
throw new Error("Value is not a string");
}
}
function printString(value: any) {
assertIsString(value);
console.log(value.toUpperCase()); // An toàn vì đã kiểm tra kiểu
}
printString("Hello");
// printString(42); // Lỗi

Phân tích:

  • asserts value is string: Khẳng định kiểu của value là string sau khi kiểm tra.
  • Hữu ích khi làm việc với các kiểu không chắc chắn.

60. Làm thế nào để sử dụng Module Augmentation trong TypeScript?

Ví dụ:

// math.d.ts
declare module "math" {
export function multiply(a: number, b: number): number;
}
// main.ts
import { multiply } from "math";
console.log(multiply(2, 3));

Phân tích:

  • declare module: Mở rộng module hiện có mà không sửa đổi mã nguồn gốc.
  • Hữu ích khi thêm tính năng hoặc định nghĩa kiểu cho module bên ngoài.

61. Sử dụng ReadonlyMap và ReadonlySet

TypeScript hỗ trợ ReadonlyMap và ReadonlySet để đảm bảo không thể thay đổi dữ liệu.

Ví dụ:

const map: ReadonlyMap<string, number> = new Map([
["one", 1],
["two", 2],
]);
// map.set("three", 3); // Lỗi: Không thể thay đổi ReadonlyMap
const set: ReadonlySet<number> = new Set([1, 2, 3]);
// set.add(4); // Lỗi: Không thể thay đổi ReadonlySet

Phân tích:

  • ReadonlyMap và ReadonlySet: Ngăn chặn mọi thay đổi đối với dữ liệu.
  • Sử dụng trong trường hợp cần bảo vệ cấu trúc dữ liệu gốc.

62. Làm thế nào để xử lý Readonly và Mutable một cách linh hoạt?

Ví dụ:

type Mutable<T> = {
-readonly [P in keyof T]: T[P];
};
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
interface User {
id: number;
name: string;
}
const user: Readonly<User> = { id: 1, name: "Alice" };
// user.id = 2; // Lỗi
const mutableUser: Mutable<User> = { id: 1, name: "Alice" };
mutableUser.id = 2; // Hợp lệ

Phân tích:

  • -readonly: Loại bỏ thuộc tính readonly.
  • readonly: Thêm thuộc tính readonly vào tất cả các thuộc tính.

63. Làm thế nào để kiểm soát Default Type Parameters?

Ví dụ:

interface Box<T = string> {
content: T;
}
const stringBox: Box = { content: "Hello" }; // T mặc định là string
const numberBox: Box<number> = { content: 42 }; // Ghi đè kiểu T

Phân tích:

  • T = string: Gán giá trị mặc định cho T nếu không được cung cấp.
  • Giúp đơn giản hóa khai báo và sử dụng các kiểu generic.

64. Kết hợp Union và Mapped Types

Ví dụ:

type Roles = "admin" | "user" | "guest";
type RolePermissions = {
[K in Roles]: K extends "admin" ? "all" : K extends "user" ? "read-write" : "read-only";
};
const permissions: RolePermissions = {
admin: "all",
user: "read-write",
guest: "read-only",
};

Phân tích:

  • [K in Roles]: Tạo cấu trúc dữ liệu từ Roles.
  • Sử dụng Conditional Types để gán giá trị dựa trên từng vai trò.

65. Sử dụng Key Remapping với Conditional Types

Ví dụ:

type RemoveId<T> = {
[K in keyof T as K extends "id" ? never : K]: T[K];
};
interface User {
id: number;
name: string;
age: number;
}
type UserWithoutId = RemoveId<User>;
const user: UserWithoutId = { name: "Alice", age: 30 }; // Hợp lệ

Phân tích:

  • K extends "id" ? never : K: Loại bỏ thuộc tính id khỏi kiểu ban đầu.
  • as: Đổi tên hoặc loại bỏ khóa trong Mapped Types.

66. Sử dụng Index Signature linh hoạt hơn

Ví dụ:

interface FlexibleObject<T> {
[key: string]: T | undefined;
}
const data: FlexibleObject<number> = {
a: 1,
b: 2,
};
console.log(data["a"]); // 1
console.log(data["z"]); // undefined

Phân tích:

  • [key: string]: T | undefined: Cho phép giá trị không tồn tại trả về undefined.
  • Hữu ích khi làm việc với các đối tượng động.

67. Tự động chuyển CamelCase trong TypeScript

Ví dụ:

type CamelCase<S extends string> = S extends `${infer P}_${infer R}`
? `${P}${Capitalize<CamelCase<R>>}`
: S;
type Result = CamelCase<"hello_world_type">; // "helloWorldType"

Phân tích:

  • Sử dụng Template Literal Types để chuyển đổi tên kiểu.
  • Capitalize: Chuyển ký tự đầu tiên thành chữ in hoa.

68. Sử dụng Tagged Template Literal Types

Ví dụ:

type Color = `#${string}`;
const red: Color = "#FF0000"; // Hợp lệ
// const invalidColor: Color = "FF0000"; // Lỗi

Phân tích:

  • #${string}: Định nghĩa kiểu chuỗi có mẫu bắt đầu bằng #.
  • Hữu ích khi làm việc với các giá trị cần định dạng cụ thể.

69. Kết hợp Generic với Interface

Ví dụ:

interface ApiResponse<T> {
data: T;
status: number;
}
const response: ApiResponse<string> = {
data: "Success",
status: 200,
};

Phân tích:

  • ApiResponse<T>: Tùy chỉnh kiểu của data dựa trên T.
  • Cho phép tái sử dụng cấu trúc chung cho nhiều loại dữ liệu khác nhau.

70. Sử dụng Dynamic Return Types với Generics

Ví dụ:

function createArray<T>(length: number, value: T): T[] {
return Array(length).fill(value);
}
const numbers = createArray(3, 42); // number[]
const strings = createArray(2, "hello"); // string[]

Phân tích:

  • T: Tham số kiểu xác định kiểu phần tử trong mảng.
  • Hàm createArray trả về mảng có kiểu động dựa trên T.

71. Đảm bảo Strictly Typed Object với Record

Ví dụ:

type Config = Record<"host" | "port", string>;
const serverConfig: Config = {
host: "localhost",
port: "8080",
};

Phân tích:

  • Record<"host" | "port", string>: Yêu cầu đối tượng có hai thuộc tính host và port với kiểu string.
  • Giúp đảm bảo cấu trúc đối tượng chính xác.

72. Làm thế nào để xử lý Intersection Types với kiểu động?

Ví dụ:

type A = { id: number; name: string };
type B = { age: number };
type AB = A & B;
const user: AB = { id: 1, name: "Alice", age: 30 }; // Hợp lệ

Phân tích:

  • A & B: Kết hợp các thuộc tính của cả hai kiểu A và B.
  • Giá trị phải thỏa mãn tất cả các thuộc tính trong cả hai kiểu.

73. Tạo Custom Utility Types

Ví dụ:

type Nullable<T> = T | null;
type User = {
id: number;
name: string;
};
type NullableUser = Nullable<User>;
const user: NullableUser = null; // Hợp lệ

Phân tích:

  • T | null: Mở rộng kiểu T để bao gồm null.
  • Hữu ích khi làm việc với các giá trị tùy chọn.

74. Làm thế nào để kiểm soát Deep Partial trong TypeScript?

Ví dụ:

type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};
interface User {
id: number;
profile: {
name: string;
address: {
city: string;
zip: number;
};
};
}
const partialUser: DeepPartial<User> = {
profile: {
address: {
city: "Wonderland",
},
},
};

Phân tích:

  • T[P] extends object ? DeepPartial<T[P]> : T[P]: Tạo kiểu tùy chọn đệ quy cho tất cả các thuộc tính, bao gồm cả thuộc tính lồng nhau.
  • Hữu ích khi cần làm việc với dữ liệu chưa đầy đủ.

75. Làm thế nào để sử dụng Typeof trong TypeScript để tái sử dụng kiểu?

Ví dụ:

const config = {
apiUrl: "https://api.example.com",
timeout: 3000,
};
type Config = typeof config;
const appConfig: Config = {
apiUrl: "https://api.new-example.com",
timeout: 5000,
};

Phân tích:

  • typeof config: Trích xuất kiểu từ đối tượng config.
  • Hữu ích để đảm bảo rằng các cấu hình mới tuân theo kiểu của cấu hình gốc.

76. Sử dụng Type Guards với In Operator

Ví dụ:

type Dog = { bark: () => void };
type Cat = { meow: () => void };
function isDog(pet: Dog | Cat): pet is Dog {
return "bark" in pet;
}
function makeSound(pet: Dog | Cat) {
if (isDog(pet)) {
pet.bark();
} else {
pet.meow();
}
}

Phân tích:

  • "bark" in pet: Kiểm tra nếu thuộc tính bark tồn tại trong đối tượng pet.
  • Dùng để phân biệt kiểu trong Union Type.

77. Sử dụng Template Literal Types để định nghĩa API cụ thể

Ví dụ:

type Endpoint = "users" | "posts" | "comments";
type HttpMethod = "GET" | "POST";
type ApiPath = `${HttpMethod} /${Endpoint}`;
const apiPath: ApiPath = "GET /users"; // Hợp lệ
// const invalidPath: ApiPath = "DELETE /users"; // Lỗi

Phân tích:

  • ${HttpMethod} /${Endpoint}: Kết hợp kiểu chuỗi để tạo các đường dẫn API cụ thể.
  • Dùng để xác thực các giá trị đầu vào có cấu trúc cụ thể.

78. Sử dụng Tuple Types với độ dài cố định

Ví dụ:

type ThreeNumbers = [number, number, number];
const numbers: ThreeNumbers = [1, 2, 3]; // Hợp lệ
// const invalidNumbers: ThreeNumbers = [1, 2]; // Lỗi

Phân tích:

  • [number, number, number]: Yêu cầu mảng có đúng ba phần tử kiểu number.
  • Hữu ích khi làm việc với các mảng có kích thước cố định.

79. Sử dụng Parameters và ReturnType để trích xuất kiểu từ hàm

Ví dụ:

function add(a: number, b: number): number {
return a + b;
}
type AddParams = Parameters<typeof add>; // [number, number]
type AddReturn = ReturnType<typeof add>; // number
const params: AddParams = [2, 3];
const result: AddReturn = add(...params);

Phân tích:

  • Parameters<typeof add>: Lấy kiểu tham số của hàm add.
  • ReturnType<typeof add>: Lấy kiểu trả về của hàm add.

80. Sử dụng Infer trong Mapped Types để xử lý kiểu phức tạp

Ví dụ:

type ExtractPromiseType<T> = T extends Promise<infer U> ? U : never;
type Result = ExtractPromiseType<Promise<number>>; // number

Phân tích:

  • infer U: Suy diễn kiểu bên trong Promise.
  • Hữu ích khi làm việc với kiểu phức tạp như Promise hoặc Observable.

81. Làm thế nào để sử dụng Awaited trong TypeScript?

Ví dụ:

type AsyncResult<T> = T extends Promise<infer U> ? U : T;
type Result = AsyncResult<Promise<string>>; // string
type DirectValue = AsyncResult<number>;    // number

Phân tích:

  • Awaited<T> (hoặc AsyncResult<T> tự tạo): Trích xuất kiểu giá trị mà Promise trả về.
  • Giúp làm việc với các hàm không đồng bộ dễ dàng hơn.

82. Làm thế nào để bảo vệ dữ liệu bằng Branding trong TypeScript?

Ví dụ:

type UserId = string & { readonly brand: unique symbol };
function createUserId(id: string): UserId {
return id as UserId;
}
const userId = createUserId("12345");
// userId = "67890"; // Lỗi: Không thể gán trực tiếp

Phân tích:

  • readonly brand: unique symbol: Tạo kiểu riêng biệt dựa trên string.
  • Hữu ích khi cần phân biệt các giá trị tương tự nhưng không thể hoán đổi.

83. Sử dụng ReadonlyTuple để ngăn chặn thay đổi

Ví dụ:

type ReadonlyTuple = readonly [number, string];
const tuple: ReadonlyTuple = [1, "hello"];
// tuple[0] = 2; // Lỗi: Tuple là readonly

Phân tích:

  • readonly [number, string]: Đảm bảo tuple không thể bị thay đổi sau khi khởi tạo.

84. Làm thế nào để xử lý kiểu động với Record và Union?

Ví dụ:

type Actions = "start" | "stop" | "pause";
type ActionHandlers = Record<Actions, () => void>;
const handlers: ActionHandlers = {
start: () => console.log("Starting"),
stop: () => console.log("Stopping"),
pause: () => console.log("Pausing"),
};

Phân tích:

  • Record<Actions, () => void>: Đảm bảo mọi giá trị trong Actions đều có handler.
  • Hữu ích khi cần ánh xạ hành động và logic.

85. Làm thế nào để tạo kiểu Exhaustive Check với never?

Ví dụ:

type Shape =
| { kind: "circle"; radius: number }
| { kind: "rectangle"; width: number; height: number };
function getArea(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "rectangle":
return shape.width * shape.height;
default:
const exhaustiveCheck: never = shape; // Lỗi nếu có trường hợp không xử lý
throw new Error(`Unhandled shape: ${exhaustiveCheck}`);
}
}

Phân tích:

  • const exhaustiveCheck: never: Đảm bảo tất cả các trường hợp trong Shape đều được xử lý.
  • Hữu ích khi mở rộng Union Type và tránh lỗi quên xử lý trường hợp mới.

86. Sử dụng Mapped Types để biến đổi thuộc tính thành tùy chọn (Optional)

Ví dụ:

type Optional<T> = {
[P in keyof T]?: T[P];
};
interface User {
id: number;
name: string;
email: string;
}
type OptionalUser = Optional<User>;
const user: OptionalUser = {
name: "Alice", // Các thuộc tính khác có thể bị bỏ qua
};

Phân tích:

  • [P in keyof T]?: T[P]: Biến tất cả các thuộc tính của T thành tùy chọn.
  • Hữu ích khi làm việc với các đối tượng chưa đầy đủ thông tin hoặc trong các biểu mẫu.

87. Sử dụng Pick để lấy một phần thuộc tính từ kiểu

Ví dụ:

interface User {
id: number;
name: string;
email: string;
}
type UserBasicInfo = Pick<User, "id" | "name">;
const user: UserBasicInfo = {
id: 1,
name: "Alice",
};

Phân tích:

  • Pick<User, "id" | "name">: Chỉ giữ lại các thuộc tính id và name từ User.
  • Dùng để tạo kiểu nhỏ gọn từ một kiểu lớn.

88. Sử dụng Omit để loại bỏ một số thuộc tính

Ví dụ:

interface User {
id: number;
name: string;
email: string;
}
type UserWithoutEmail = Omit<User, "email">;
const user: UserWithoutEmail = {
id: 1,
name: "Alice",
};

Phân tích:

  • Omit<User, "email">: Loại bỏ thuộc tính email khỏi kiểu User.
  • Hữu ích khi cần tránh các thuộc tính không cần thiết trong một ngữ cảnh cụ thể.

89. Sử dụng Partial để làm tất cả thuộc tính thành tùy chọn

Ví dụ:

interface User {
id: number;
name: string;
email: string;
}
type PartialUser = Partial<User>;
const user: PartialUser = {
name: "Alice",
};

Phân tích:

  • Partial<User>: Biến tất cả các thuộc tính trong kiểu User thành tùy chọn.
  • Tương tự như tự tạo Mapped Type nhưng có sẵn trong TypeScript.

90. Sử dụng Required để làm tất cả thuộc tính bắt buộc

Ví dụ:

interface User {
id?: number;
name?: string;
}
type CompleteUser = Required<User>;
const user: CompleteUser = {
id: 1,
name: "Alice", // Tất cả thuộc tính giờ đều bắt buộc
};

Phân tích:

  • Required<User>: Biến tất cả các thuộc tính tùy chọn trong kiểu User thành bắt buộc.
  • Hữu ích khi cần đảm bảo mọi thuộc tính đều phải được khởi tạo.

91. Sử dụng Readonly để làm tất cả thuộc tính chỉ đọc

Ví dụ:

interface User {
id: number;
name: string;
}
type ReadonlyUser = Readonly<User>;
const user: ReadonlyUser = {
id: 1,
name: "Alice",
};
// user.id = 2; // Lỗi: Thuộc tính chỉ đọc

Phân tích:

  • Readonly<User>: Biến tất cả các thuộc tính trong kiểu User thành readonly.
  • Dùng để bảo vệ dữ liệu không bị thay đổi.

92. Tạo kiểu Conditional Types cho tham số

Ví dụ:

type IsString<T> = T extends string ? "Yes" : "No";
type Test1 = IsString<string>; // "Yes"
type Test2 = IsString<number>; // "No"

Phân tích:

  • T extends string ? "Yes" : "No": Kiểm tra kiểu T có phải là string hay không.
  • Conditional Types hữu ích khi cần xử lý logic kiểu.

93. Sử dụng Index Access Types để trích xuất kiểu của thuộc tính

Ví dụ:

interface User {
id: number;
name: string;
}
type UserIdType = User["id"]; // number

Phân tích:

  • User["id"]: Trích xuất kiểu của thuộc tính id trong User.
  • Hữu ích khi cần tái sử dụng kiểu của một thuộc tính cụ thể.

94. Làm thế nào để sử dụng Union với Index Signatures?

Ví dụ:

interface Config {
[key: string]: string | number;
}
const config: Config = {
host: "localhost",
port: 8080,
};

Phân tích:

  • [key: string]: string | number: Cho phép đối tượng có các thuộc tính động với kiểu string hoặc number.
  • Dùng trong các trường hợp cần cấu hình linh hoạt.

95. Sử dụng Union để tạo kiểu kết hợp

Ví dụ:

type UserRole = "admin" | "user" | "guest";
function getRole(role: UserRole) {
console.log(`Role: ${role}`);
}
getRole("admin"); // Hợp lệ
// getRole("manager"); // Lỗi: Không nằm trong Union

Phân tích:

  • "admin" | "user" | "guest": Định nghĩa kiểu Union.
  • Đảm bảo giá trị chỉ nằm trong các giá trị hợp lệ được chỉ định.

96. Tạo kiểu Intersection để kết hợp thuộc tính

Ví dụ:

type WithTimestamp = { createdAt: Date; updatedAt: Date };
type User = { id: number; name: string };
type UserWithTimestamp = User & WithTimestamp;
const user: UserWithTimestamp = {
id: 1,
name: "Alice",
createdAt: new Date(),
updatedAt: new Date(),
};

Phân tích:

  • User & WithTimestamp: Kết hợp hai kiểu để tạo một kiểu mới.
  • Hữu ích khi cần mở rộng kiểu cho các đối tượng phức tạp.

97. Sử dụng NonNullable để loại bỏ null và undefined

Ví dụ:

type StrictString = NonNullable<string | null | undefined>;
const value: StrictString = "Hello"; // Hợp lệ
// const invalidValue: StrictString = null; // Lỗi

Phân tích:

  • NonNullable<T>: Loại bỏ null và undefined khỏi kiểu T.
  • Hữu ích khi cần đảm bảo giá trị luôn tồn tại.

98. Làm thế nào để tạo kiểu Deep Required?

Ví dụ:

type DeepRequired<T> = {
[P in keyof T]-?: T[P] extends object ? DeepRequired<T[P]> : T[P];
};
interface User {
id?: number;
profile?: {
name?: string;
address?: {
city?: string;
};
};
}
type StrictUser = DeepRequired<User>;
const user: StrictUser = {
id: 1,
profile: {
name: "Alice",
address: {
city: "Wonderland",
},
},
};

Phân tích:

  • [P in keyof T]-?: Loại bỏ optional modifier (?) từ tất cả các thuộc tính.
  • Đệ quy để áp dụng cho các thuộc tính lồng nhau.

Để lại một bình luận