Sửa lỗi TypeScript 'Enum Is Not Assignable': Type 'Status.Active' is not assignable to type 'Status.Inactive'

intermediate🔵 TypeScript2026-03-23| TypeScript 4.x / 5.x, mọi hệ điều hành (Windows, macOS, Linux), mọi editor hỗ trợ TypeScript

Error Message

Type 'Status.Active' is not assignable to type 'Status.Inactive'. ts(2322)
#typescript#enum#comparison#type

Lỗi Gặp Phải

Lỗi này xuất hiện khi bạn cố truyền hoặc gán các thành viên enum như thể chúng có thể hoán đổi cho nhau:

enum Status {
  Active,
  Inactive,
  Pending,
}

function setStatus(s: Status.Inactive) {
  // ...
}

const current = Status.Active;
setStatus(current); // ❌ Type 'Status.Active' is not assignable to type 'Status.Inactive'. ts(2322)

Lỗi tương tự xảy ra khi gán giữa các biến có kiểu cụ thể:

let a: Status.Active = Status.Active;
let b: Status.Inactive = a; // ❌ ts(2322)

Nguyên Nhân

TypeScript xem mỗi thành viên enum là một kiểu literal riêng biệt — không chỉ là một giá trị, mà là một kiểu thực sự khác nhau. Vì vậy Status.ActiveStatus.Inactive khác nhau hoàn toàn như truefalse ở cấp độ kiểu dữ liệu.

Ở runtime? Chúng chỉ là các số (0 và 1). Nhưng trình kiểm tra kiểu không quan tâm đến điều đó. Một biến được khai báo kiểu Status.Inactive sẽ chỉ chấp nhận Status.Inactive — không có ngoại lệ.

Numeric enum còn có thêm một điểm đặc biệt đáng lưu ý: chúng chấp nhận các số nguyên thuần túy (nên let s: Status = 0 vẫn biên dịch được). Tuy nhiên các thành viên enum vẫn không thể gán cho nhau khi kiểu của chúng khác nhau.

Cách Sửa 1: Mở Rộng Kiểu Thành Toàn Bộ Enum

Chín lần trong mười, đây là cách sửa đúng. Nếu hàm nên chấp nhận bất kỳ giá trị Status nào, hãy khai báo tham số kiểu Status thay vì Status.Inactive:

// ❌ Trước — vô tình bị quá hẹp
function setStatus(s: Status.Inactive) { ... }

// ✅ Sau — chấp nhận bất kỳ giá trị Status nào
function setStatus(s: Status) { ... }

const current = Status.Active;
setStatus(current); // OK

Kiểu quá hẹp này thường xuất hiện do autocomplete — bạn gõ Status., chọn Inactive, và TypeScript khóa kiểu literal lại trước khi bạn kịp nhận ra.

Cách Sửa 2: Dùng Union Các Thành Viên Cụ Thể

Cần chấp nhận một số thành viên nhưng không phải tất cả? Kiểu union giúp thể hiện rõ ràng ý định:

function handleResolved(s: Status.Active | Status.Inactive) {
  console.log(s);
}

handleResolved(Status.Active);   // ✅
handleResolved(Status.Inactive); // ✅
handleResolved(Status.Pending);  // ❌ Bị từ chối đúng như mong đợi

Cách Sửa 3: Dùng Type Guard Trước Khi Gán

Khi bạn đã có một giá trị Status rộng nhưng cần truyền vào nơi yêu cầu một thành viên cụ thể, hãy dùng điều kiện kiểm tra trước. Phân tích luồng điều khiển của TypeScript sẽ tự xử lý phần còn lại:

function requireInactive(s: Status.Inactive) {
  console.log('Inactive:', s);
}

function process(s: Status) {
  if (s === Status.Inactive) {
    requireInactive(s); // ✅ TypeScript thu hẹp s thành Status.Inactive tại đây
  }
}

Bên trong khối if đó, TypeScript biết s chỉ có thể là Status.Inactive. Phép gán trở nên an toàn mà không cần ép kiểu.

Cách Sửa 4: Type Assertion (Phương Án Cuối Cùng)

Chỉ dùng cách này khi bạn chắc chắn giá trị runtime là đúng nhưng TypeScript không thể chứng minh được — ví dụ như sau khi lấy giá trị từ API bên ngoài:

const s = getStatusFromAPI() as Status.Inactive;
requireInactive(s); // ✅ — nhưng bạn đang bỏ qua kiểm tra kiểu

Nếu bạn thấy mình phải viết as SomeEnum.Member ở nhiều hơn một hoặc hai chỗ, thì thiết kế kiểu cần được xem xét lại — không phải thêm assertion.

Cách Sửa 5: Bỏ Enum, Dùng String Literal Union

Nhiều codebase TypeScript hiện đại bỏ qua enum hoàn toàn. String literal union đơn giản hơn, tạo ra thông báo lỗi rõ ràng hơn, và tránh được toàn bộ sự nhầm lẫn về kiểu literal:

// Thay vì dùng:
enum Status {
  Active = 'active',
  Inactive = 'inactive',
  Pending = 'pending',
}

// Hãy dùng:
type Status = 'active' | 'inactive' | 'pending';

function setStatus(s: Status) { ... }

setStatus('active');   // ✅
setStatus('inactive'); // ✅
setStatus('unknown');  // ❌ Bị từ chối đúng như mong đợi

Không có quirk của enum, không có runtime object, không phải lo về các giá trị được nhúng vào. Chỉ là các chuỗi với ràng buộc được trình biên dịch kiểm soát.

Cách Sửa 6: const Enum Cũng Gặp Vấn Đề Tương Tự

const enum nhúng các giá trị vào lúc biên dịch, nhưng quy tắc kiểu hoàn toàn giống nhau. Cách sửa tương tự — mở rộng kiểu hoặc dùng union:

const enum Direction {
  Up,
  Down,
}

// ❌ Quá hẹp
let d: Direction.Up = Direction.Down; // ts(2322)

// ✅ Đã mở rộng
let d: Direction = Direction.Down; // OK

Kiểm Tra Lại

Sau khi áp dụng cách sửa, hãy xác nhận lỗi đã được giải quyết:

  • Đường gạch đỏ dưới phép gán sẽ biến mất ngay trong editor của bạn.
  • Chạy kiểm tra kiểu toàn dự án để phát hiện những gì editor có thể bỏ sót:
npx tsc --noEmit
# Không có output = không có lỗi

  • Nếu bạn dùng type guard (Cách sửa 3), hãy thêm một test nhanh kiểm tra cả nhánh khớp lẫn không khớp — xác nhận guard hoạt động đúng ở runtime, không chỉ ở compile time.

Tóm Tắt Nhanh

  • Tham số quá hẹp → đổi kiểu từ Status.X thành Status
  • Cần một tập con các thành viên → dùng Status.X | Status.Y
  • Thu hẹp kiểu trong điều kiện → dùng if (s === Status.X) trước khi truyền
  • Muốn tránh hoàn toàn sự phức tạp của enum → chuyển sang string literal union

Phòng Tránh

Nguyên nhân gốc rễ hầu như luôn giống nhau: một biến hoặc tham số vô tình được khai báo kiểu là một thành viên enum cụ thể (Status.Active) trong khi ý định là toàn bộ enum (Status). Autocomplete thường là thủ phạm — bạn điền vào một giá trị ban đầu và TypeScript khóa kiểu literal lại.

Ba thói quen giúp phòng tránh điều này:

  • Viết annotation kiểu tường minh cho biến enum: let s: Status = Status.Active thay vì để TypeScript tự suy ra Status.Active từ vế phải.
  • Ưu tiên dùng string enum hoặc string literal union. Thông báo lỗi dễ đọc hơn và ít có các trường hợp đặc biệt gây bất ngờ hơn.
  • Bật chế độ strict trong tsconfig.json. Nó phát hiện những điểm không khớp ngay tại nơi khai báo, trước khi chúng lan rộng qua ba tầng lời gọi hàm và trở nên khó hiểu.

Related Error Notes