Cách sửa lỗi TypeScript: 'string' is not assignable to type 'never' (TS2322)

intermediate🔵 TypeScript2026-07-27| TypeScript 3.0+, Node.js, React, hoặc bất kỳ môi trường nào dựa trên TS.

Error Message

Type 'string' is not assignable to type 'never'. (TS2322)
#typescript#kieu-never#exhaustive-checking#an-toan-kieu

Tóm tắt: Cách khắc phục nhanh

Bạn có lẽ đã gặp lỗi này khi cố gắng thực hiện kiểm tra tính đầy đủ (exhaustive checking) trong một khối switch hoặc if/else. Trình biên dịch đang báo lỗi vì nó cho rằng một giá trị vẫn có thể lọt qua được trường hợp default của bạn.

Để khắc phục, hãy kiểm tra hai điều sau:

  • Thu hẹp đầu vào: Biến của bạn phải là một union cụ thể (như 'success' | 'error'), chứ không phải là một string chung chung.
  • Xử lý mọi trường hợp: Mọi giá trị có thể có trong union đó phải được xử lý bởi một case trước khi nó đi tới dòng gán never.
// Mô hình chuẩn để thành công
type Status = 'active' | 'inactive';

function handleStatus(status: Status) {
  switch (status) {
    case 'active':
      return 1;
    case 'inactive':
      return 0;
    default:
      // Mã này chỉ biên dịch được nếu tất cả các biến thể của 'Status' đã được xử lý ở trên
      const _exhaustiveCheck: never = status;
      return _exhaustiveCheck;
  }
}

Kịch bản

Hãy tưởng tượng bạn đang tái cấu trúc (refactor) một dịch vụ thanh toán. Bạn quyết định trở thành một kỹ sư chủ động và triển khai việc kiểm tra tính đầy đủ. Mô hình này đóng vai trò như một cơ chế cảnh báo; nếu một đồng nghiệp thêm phương thức thanh toán mới vào tháng sau, trình biên dịch sẽ báo lỗi cho đến khi họ xử lý logic mới đó. Đây là một lưới an toàn tuyệt vời.

But then you see it. Thay vì một lời nhắc nhở hữu ích, trình biên dịch lại đưa ra một lỗi khó hiểu:

Type 'string' is not assignable to type 'never'. (TS2322)

Điều này có vẻ mâu thuẫn. Bạn đã viết các trường hợp (cases), nhưng TypeScript vẫn khăng khăng rằng biến đó là một string chung chung và từ chối cho phép bạn gán nó cho never.

Tại sao điều này lại xảy ra?

Trong TypeScript, never là kiểu "bất khả thi". Bằng cách gán một biến cho never trong khối default, bạn đang đưa ra một khẳng định mạnh mẽ: "Về mặt logic, mã không bao giờ có thể chạm tới dòng này."

Lỗi TS2322 xuất hiện khi TypeScript tìm thấy một lỗ hổng trong logic của bạn. Dưới đây là ba "thủ phạm" phổ biến nhất:

1. Đầu vào của bạn quá rộng

TypeScript rất thận trọng. Nếu đối số hàm của bạn được định kiểu là một string chung chung thay vì một union cụ thể của các literal, trình biên dịch sẽ giả định rằng bất kỳ văn bản nào cũng có thể đi vào hàm. Ngay cả khi bạn xử lý 'A' và 'B', đầu vào vẫn có thể là 'Z'. Vì 'Z' sẽ rơi vào khối default, kiểu dữ liệu ở đó là string, chứ không phải never.

2. Bẫy "Trường hợp bị bỏ sót"

Điều này thường xảy ra sau một bản cập nhật. Giả sử bạn đã mở rộng một union từ 2 biến thể lên 3 (ví dụ: thêm 'pending' vào 'success' | 'failed') nhưng không cập nhật câu lệnh switch của mình. TypeScript nhận thấy rằng 'pending' vẫn chưa được xử lý. Nó cố gắng chuyển giá trị đó vào khối default của bạn, gây ra lỗi.

3. Nới rộng kiểu (Type Widening)

Logic phức tạp hoặc ép kiểu (casting) không đúng cách đôi khi có thể khiến một kiểu literal cụ thể bị "nới rộng" trở lại thành string. Một khi tính cụ thể đó bị mất đi, trình biên dịch không còn có thể đảm bảo rằng khối default là không thể chạm tới.

Cách khắc phục

Cách 1: Thắt chặt kiểu đầu vào

Hãy bắt đầu bằng cách kiểm tra nguồn dữ liệu. Nếu nó đến từ phản hồi API hoặc đầu vào biểu mẫu, nó có thể được định kiểu lỏng lẻo. Hãy sử dụng Type Guard hoặc một định nghĩa union cụ thể để thu hẹp nó.

// ❌ Vấn đề: 'status' chỉ là một string
function process(status: string) {
  switch (status) {
    case 'open': return;
    default:
      const _ex: never = status; // Error: string is not never
  }
}

// ✅ Giải pháp: Sử dụng một union chặt chẽ
type TicketStatus = 'open' | 'closed';
function process(status: TicketStatus) {
  switch (status) {
    case 'open': return;
    case 'closed': return;
    default:
      const _ex: never = status; // Không còn lỗi!
  }
}

Cách 2: Sử dụng hàm hỗ trợ 'assertNever'

Việc gán thủ công cũng tốt, nhưng một hàm tiện ích sẽ sạch sẽ hơn. Nó cung cấp một lỗi runtime rõ ràng nếu ai đó vượt qua trình biên dịch bằng cách sử dụng as any. Nó cũng làm cho ý định của bạn trở nên rõ ràng đối với các nhà phát triển khác.

function assertNever(x: never): never {
  throw new Error("Unexpected value: " + x);
}

type Plan = 'basic' | 'pro' | 'enterprise';

function getPrice(plan: Plan) {
  switch (plan) {
    case 'basic': return 10;
    case 'pro': return 20;
    case 'enterprise': return 50;
    default:
      return assertNever(plan); 
      // Nếu bạn thêm gói 'premium' sau này, dòng này sẽ ngay lập tức báo lỗi TS2345
  }
}

Cách 3: Cẩn thận với Enum

Khi làm việc với Enum, hãy đảm bảo bạn đang thực hiện switch trên chính kiểu Enum đó. Việc so sánh một Enum với một chuỗi thô có thể làm hỏng khả năng thu hẹp kiểu chính xác của trình biên dịch.

enum UserRole { Admin, Editor }

function checkAccess(role: UserRole) {
  switch (role) {
    case UserRole.Admin: return true;
    case UserRole.Editor: return true;
    default:
      const _check: never = role; // Hoạt động như mong đợi
  }
}

Các bước xác minh

  • Kiểm tra biến: Di chuột qua biến bên trong khối default của bạn trong VS Code. Nếu tooltip hiển thị (parameter) status: "pending", đơn giản là bạn đã quên xử lý trường hợp cụ thể đó. Nếu nó hiển thị status: string, kiểu đầu vào của bạn quá lỏng lẻo.
  • Tìm kiếm các đoạn ép kiểu: Tìm kiếm as string trong mã nguồn của bạn. Việc ép kiểu quá mức thường loại bỏ các kiểu union mà việc kiểm tra tính đầy đủ dựa vào.
  • Kiểm tra lỗ hổng: Thêm một case tạm thời cho giá trị mà TypeScript đang báo lỗi. Nếu lỗi biến mất, bạn đã xác nhận được một nhánh bị thiếu trong logic của mình.

Tổng kết

Lỗi TS2322 không phải là một lỗi của trình biên dịch; đó là trình biên dịch đang làm chính xác những gì bạn đã yêu cầu. Nó đã tìm thấy một lỗ hổng trong logic của bạn, nơi một giá trị có thể lọt qua. Hãy khắc phục nó bằng cách thu hẹp đầu vào hoặc bằng cách xử lý rõ ràng mọi biến thể có thể có trong union của bạn.

Related Error Notes