
100 câu hỏi phỏng vấn typescript kèm lời giải chi tiết từ A-Z
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ếnacó kiểunumber. 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 địnhbcó kiểustring.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ượngUser, yêu cầu các thuộc tính nhưid(number),name(string).agelà tùy chọn vì có dấu?.const user: User: Biếnuserphải tuân theo cấu trúc củaUser.- Nếu bỏ qua
idhoặcname, 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ả
interfacevàtypeđều dùng để định nghĩa kiểu đối tượng. interfacehỗ trợ kế thừa (extends).typelinh 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
interfacenếu chỉ định nghĩa cấu trúc đối tượng. - Dùng
typenế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:
Tlà 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ểunumber, nênargsẽ lànumber.identity<string>: Gọi hàm với kiểustring.
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àstringhoặcnumber).typeof value === "string": Hẹp phạm vi kiểu (narrowing) để TypeScript biếtvaluelà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ủaT.readonly: Gán thuộc tính chỉ đọc.- Kết quả là
ReadonlyUsercó 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ểuunion, cho phép giá trị củavaluelàstringhoặcnumber.- 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ểuintersection, yêu cầu giá trị phải có cảnamevàage.person: Đối tượng phải thỏa mãn tất cả các thuộc tính từHasNamevà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ểuDirection.
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ếuanimalcó phương thứcbark.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ếuaddresskhông tồn tại, sẽ trả vềundefinedthay 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
throwErrorkhô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
classkhi cần cả dữ liệu và logic. - Dùng
interfacenế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 classkhi cần định nghĩa logic chung. - Dùng
interfacekhi 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ểusomeValuethànhstring.- 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 trongRolesđược ánh xạ đến kiểustring[].- 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ểuUser, kết quả là"id" | "name".- Hàm
getProperty: Ràng buộckeyphải là một khóa hợp lệ củaobj.
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ínhidtrongUser.- 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ểuRolevàActionthà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ếuTlàstring.- Kết quả là
truehoặcfalsetù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
Tlà 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
userphải có cảnamevà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ểuany.strictNullChecks: Không cho phép sử dụngnullhoặcundefinedmà 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ểuPointđể 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ểustringhoặcnumber.- 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ểuAddressđể 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ỏnullvàundefinedkhỏi kiểu.- Hữu ích khi muốn đảm bảo giá trị không bao giờ là
nullhoặcundefined.
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ủakind.
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ínhnamelà 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ínhidkhô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ợptuplevớirest parametersđể giới hạn kiểu và số lượng tham số.- Đảm bảo
logchỉ 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ỏ modifierreadonlykhỏi các thuộc tính.[P in keyof T]: Lặp qua các thuộc tính của kiểuT.
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ụnginferđể 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ụngkindlà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ảoTphải có thuộc tínhid.logWithId(user): Chỉ hợp lệ nếu đối tượng có kiểuHasId.
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ụngreadonlyđệ 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ỏbooleankhỏ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ểuunionthànhintersection.- Á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ểuT.- 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ủaTthà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ểuT.
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ủavaluelàstringsau 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:
ReadonlyMapvà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ínhreadonly.readonly: Thêm thuộc tínhreadonlyvà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 choTnế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ínhidkhỏi kiểu ban đầu.as: Đổi tên hoặc loại bỏ khóa trongMapped 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ủadatadựa trênT.- 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
createArraytrả về mảng có kiểu động dựa trênT.
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ínhhostvàportvới kiểustring.- 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ểuAvà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ểuTđể bao gồmnull.- 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ượngconfig.- 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ínhbarktồn tại trong đối tượngpet.- 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ểunumber.- 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àmadd.ReturnType<typeof add>: Lấy kiểu trả về của hàmadd.
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 trongPromise.- Hữu ích khi làm việc với kiểu phức tạp như
PromisehoặcObservable.
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ặcAsyncResult<T>tự tạo): Trích xuất kiểu giá trị màPromisetrả 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ênstring.- 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ị trongActionsđề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 trongShapeđều được xử lý.- Hữu ích khi mở rộng
Union Typevà 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ủaTthà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ínhidvànametừ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ínhemailkhỏi kiểuUser.- 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ểuUserthành tùy chọn.- Tương tự như tự tạo
Mapped Typenhư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ểuUserthà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ểuUserthànhreadonly.- 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ểuTcó phải làstringhay 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ínhidtrongUser.- 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ểustringhoặcnumber.- 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ỏnullvàundefinedkhỏi kiểuT.- 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ỏoptionalmodifier (?) từ tất cả các thuộc tính.- Đệ quy để áp dụng cho các thuộc tính lồng nhau.







-
Tagged flutter, mobile-dev, react-native