Sửa lỗi TypeScript: Argument of type 'readonly string[]' is not assignable to parameter of type 'string[]'

intermediate🔵 TypeScript2026-06-06| TypeScript 3.4+, Node.js, React/Frontend frameworks sử dụng chế độ kiểm tra kiểu nghiêm ngặt (strict type checking).

Error Message

Argument of type 'readonly string[]' is not assignable to parameter of type 'string[]'. The type 'readonly string[]' is 'readonly' and cannot be assigned to the mutable type 'string[]'.
#typescript#readonly#mảng#hệ-thống-kiểu#bất-biến#spread

TL;DR: Cách sửa nhanh

Sao chép mảng bằng toán tử spread để tạo một phiên bản có thể thay đổi (mutable). Chỉ mất một dòng mã:

const readonlyArray: readonly string[] = ['debug', 'info', 'error'];

// Tạo một bản sao có thể thay đổi
processData([...readonlyArray]);

Để có giải pháp lâu dài hơn, hãy thay đổi signature của hàm để chấp nhận readonly string[]. Điều này giúp tránh việc chiếm dụng bộ nhớ không cần thiết khi sao chép mảng.

Kịch bản Production lúc 2 giờ sáng

Bạn đang triển khai một bản vá nóng (hotfix). Bạn lấy dữ liệu cấu hình từ các nguồn như Zod, Prisma, hoặc một hằng số được định nghĩa với as const. Bạn truyền dữ liệu này vào một hàm tiện ích đã sử dụng hàng trăm lần. Đột nhiên, quá trình build thất bại.

Argument of type 'readonly string[]' is not assignable to parameter of type 'string[]'. 
The type 'readonly string[]' is 'readonly' and cannot be assigned to the mutable type 'string[]'.

TypeScript đang đóng vai trò một vệ sĩ nghiêm ngặt ở đây. Bạn có một mảng đã hứa là sẽ không bao giờ thay đổi. Giờ đây, bạn đang cố gắng đưa nó cho một hàm có quyền thay đổi nó. TypeScript sẽ không để rủi ro đó xảy ra.

Nguyên nhân gốc rễ: Sự không khớp về thỏa ước (Contract)

Trong TypeScript, string[] chỉ là cách viết tắt của Array<string>. Kiểu dữ liệu này bao gồm các phương thức thay đổi dữ liệu trực tiếp (in-place) như .push(), .sort(), hoặc .splice().

Kiểu readonly string[] thì khác. Nó loại bỏ rõ ràng các phương thức thay đổi đó. Nếu TypeScript cho phép bạn truyền một mảng readonly vào một hàm yêu cầu string[] tiêu chuẩn, hàm đó có thể gọi .push('new-item'). Điều này sẽ phá vỡ thỏa ước về tính bất biến của dữ liệu gốc. Điều này thường dẫn đến các lỗi runtime cực kỳ khó tìm trong các ứng dụng quản lý state lớn.

Các phương pháp xử lý

1. Cập nhật Function Signature (Khuyến nghị)

Kiểm tra xem hàm của bạn có thực sự thay đổi mảng hay không. Nếu nó chỉ đọc dữ liệu — như sử dụng .map(), .find(), hoặc kiểm tra .length — hãy thay đổi kiểu tham số. Đây là cách tiếp cận kiến trúc sạch nhất.

// Thay đổi cái này:
function logMessages(messages: string[]) {
  messages.forEach(m => console.log(m));
}

// Thành cái này:
function logMessages(messages: readonly string[]) {
  messages.forEach(m => console.log(m));
}

Điều này giúp hàm của bạn linh hoạt hơn. Bây giờ nó có thể xử lý cả mảng có thể thay đổi và mảng bất biến mà không gặp lỗi.

2. Tạo một bản sao có thể thay đổi

Đôi khi bạn rơi vào thế bí. Có thể bạn đang sử dụng một thư viện bên thứ ba cũ mà bạn không thể chỉnh sửa. Nếu hàm cần .sort() dữ liệu hoặc bạn đơn giản là không thể thay đổi kiểu dữ liệu, hãy tạo một bản sao nông (shallow copy).

const config: readonly string[] = ['admin', 'user'];

// Sử dụng toán tử spread
someExternalLibraryFunction([...config]);

// Hoặc sử dụng .slice() cho các môi trường cũ hơn
someExternalLibraryFunction(config.slice());

Cảnh báo: Spread chỉ tạo ra một bản sao nông. Nếu mảng của bạn chứa 1.000 đối tượng, mảng mới vẫn trỏ đến cùng 1.000 đối tượng đó trong bộ nhớ. Việc sửa đổi thuộc tính của một đối tượng bên trong hàm vẫn sẽ ảnh hưởng đến dữ liệu gốc.

3. Ép kiểu - Type Assertion (Lối thoát hiểm)

Chỉ sử dụng cách này nếu bạn chắc chắn 100% rằng hàm nhận dữ liệu sẽ không chạm vào dữ liệu đó. Nó bảo trình biên dịch hãy "tin tôi đi", điều này bỏ qua các kiểm tra an toàn mà bạn vốn dĩ sử dụng TypeScript để có được.

const data: readonly string[] = fetchData();

// Ép kiểu để xử lý như mảng có thể thay đổi
handleData(data as string[]);

Các bước xác minh

Xác nhận giải pháp hoạt động ổn định trong quy trình của bạn:

  • Kiểm tra trình biên dịch: Chạy npx tsc --noEmit. Nếu terminal không hiện lỗi, lỗi kiểu dữ liệu đã được giải quyết.
  • Kiểm tra bộ nhớ: Nếu bạn đang sao chép một mảng có hơn 50.000 phần tử bên trong một vòng lặp, hãy theo dõi mức sử dụng bộ nhớ. Cập nhật signature (Cách 1) luôn tốt hơn cho hiệu năng.
  • Logic Test: Nếu bạn đã cập nhật hàm thành readonly, hãy kiểm tra xem nó có sử dụng .sort() không. Vì .sort() thay đổi dữ liệu trực tiếp, TypeScript sẽ báo một lỗi mới bên trong hàm đó, buộc bạn phải sử dụng .toSorted() hoặc một bản sao.

Related Error Notes