Lập trình

TypeScript strict mode: Cách giảm lỗi runtime từ sớm

TypeScript không tự động làm ứng dụng an toàn chỉ vì file có đuôi .ts. Nếu dự án dùng nhiều any, bỏ qua null và ép kiểu tùy tiện, nhiều lỗi vẫn chỉ xuất hiện khi người dùng thao tác. strict biến trình biên dịch thành một lớp kiểm tra thiết kế hữu ích hơn.

TypeScript strict mode: Cách giảm lỗi runtime từ sớm

TypeScript không tự động làm ứng dụng an toàn chỉ vì file có đuôi .ts. Nếu dự án dùng nhiều any, bỏ qua null và ép kiểu tùy tiện, nhiều lỗi vẫn chỉ xuất hiện khi người dùng thao tác. strict biến trình biên dịch thành một lớp kiểm tra thiết kế hữu ích hơn.

Bật strict mode có thể làm một dự án cũ xuất hiện hàng trăm lỗi. Điều đó không có nghĩa code vừa hỏng; compiler đang chỉ ra những giả định vốn đã tồn tại nhưng chưa được kiểm chứng.

strict thực sự bật những gì?

Trong tsconfig.json, cấu hình nền tảng rất ngắn:

{
  "compilerOptions": {
    "strict": true,
    "noEmit": true
  }
}

strict bật một họ kiểm tra như strictNullChecks, noImplicitAny, strictFunctionTypes, strictPropertyInitializationuseUnknownInCatchVariables. Các phiên bản TypeScript tương lai có thể bổ sung kiểm tra mới vào nhóm này, vì vậy cần chạy type-check trong CI khi nâng cấp.

strictNullChecks loại bỏ giả định nguy hiểm

const user = users.find((item) => item.id === selectedId);

// Error under strictNullChecks: user may be undefined.
console.log(user.name);

Cách sửa không phải luôn thêm dấu !. Hãy thể hiện đúng nghiệp vụ:

if (!user) {
  throw new Error('Selected user was not found');
}

console.log(user.name);

Nếu không tìm thấy là tình huống bình thường, hàm nên trả về User | undefined và caller phải xử lý. Nếu đó là invariant, hãy kiểm tra một lần ở biên hệ thống rồi dùng kiểu hẹp hơn bên trong.

Không thay any bằng ép kiểu

any tắt kiểm tra kiểu và có xu hướng lan truyền. Với dữ liệu từ API, local storage hoặc message queue, điểm bắt đầu trung thực hơn là unknown:

function parseProfile(input: unknown): Profile {
  if (!isProfile(input)) {
    throw new Error('Invalid profile payload');
  }

  return input;
}

Type guard hoặc thư viện schema validation giúp thu hẹp unknown sau khi đã kiểm tra runtime. Kiểu TypeScript bị xóa khi build nên không thể thay thế validation ở ranh giới dữ liệu.

Xử lý lỗi trong catch đúng cách

JavaScript cho phép ném bất kỳ giá trị nào, không chỉ Error. Vì vậy strict mode xem biến trong catchunknown:

try {
  await saveOrder();
} catch (error: unknown) {
  const message = error instanceof Error
    ? error.message
    : 'Unknown error';

  reportFailure(message);
}

Đoạn kiểm tra nhỏ này tránh việc truy cập error.message trên một giá trị không có thuộc tính đó.

Dùng union phân biệt thay cho nhiều boolean

type LoadState =
  | { status: 'idle' }
  | { status: 'loading' }
  | { status: 'success'; data: User[] }
  | { status: 'error'; message: string };

So với ba boolean isLoading, hasError, hasData, discriminated union loại bỏ các trạng thái mâu thuẫn. Một switch theo status cũng giúp compiler thu hẹp đúng dữ liệu có sẵn.

Kiểm tra đầy đủ bằng never

function renderState(state: LoadState): string {
  switch (state.status) {
    case 'idle': return 'Ready';
    case 'loading': return 'Loading';
    case 'success': return `${state.data.length} users`;
    case 'error': return state.message;
    default: return assertNever(state);
  }
}

function assertNever(value: never): never {
  throw new Error(`Unhandled state: ${JSON.stringify(value)}`);
}

Khi thêm một trạng thái mới mà quên xử lý, type-check sẽ báo lỗi tại assertNever.

Dùng satisfies để kiểm tra mà vẫn giữ inference

type RouteName = 'home' | 'account';

const routes = {
  home: '/',
  account: '/account',
} satisfies Record<RouteName, string>;

satisfies xác minh object phù hợp với contract nhưng vẫn giữ kiểu cụ thể của biểu thức. Nó hữu ích cho config, route map và dictionary cần đủ key.

Hạn chế non-null assertion và type assertion

value!value as SomeType nói với compiler rằng lập trình viên biết rõ hơn, nhưng không tạo kiểm tra runtime. Chúng phù hợp tại một số ranh giới framework đã được đảm bảo, song không nên là cách mặc định để “dọn” lỗi strict.

  • Ưu tiên kiểm tra điều kiện hoặc optional chaining.
  • Dùng assertion function khi một invariant được kiểm tra tập trung.
  • Validate dữ liệu ngoài hệ thống trước khi cast.
  • Không dùng as unknown as T để vượt contract.

Lộ trình bật strict cho dự án cũ

  1. Tạo lệnh type-check riêng và đưa vào CI.
  2. Bật strictNullChecks trước nếu dự án có nhiều lỗi null.
  3. Sửa từ module biên: API client, parser, shared types.
  4. Thay any bằng unknown tại đầu vào không tin cậy.
  5. Dùng danh sách lỗi làm backlog, không thêm lỗi mới.
  6. Bật toàn bộ strict, sau đó cân nhắc noUncheckedIndexedAccessexactOptionalPropertyTypes theo nhu cầu.
Không nên tắt strict toàn dự án chỉ vì một thư viện có type chưa tốt. Hãy cô lập adapter, bổ sung type declaration hoặc dùng một ngoại lệ nhỏ có ghi chú.

Checklist review code TypeScript

  • Dữ liệu bên ngoài bắt đầu từ unknown và được validate.
  • Không truy cập giá trị có thể null/undefined khi chưa kiểm tra.
  • Union thể hiện trạng thái hợp lệ thay cho tổ hợp boolean.
  • Assertion có lý do rõ ràng và phạm vi nhỏ.
  • Type-check chạy trong CI, tách khỏi bundler nếu cần.
  • Nâng TypeScript có bước đọc release notes và xử lý lỗi strict mới.

Nguồn tham khảo

Thảo luận

Bình luận 0

Đăng nhập để bình luận

Bạn cần có tài khoản để tham gia thảo luận và trả lời độc giả khác.

Đăng nhậpĐăng ký

Chưa có bình luận. Hãy là người đầu tiên chia sẻ ý kiến.