0

is-kit và Zod khác nhau như thế nào? So sánh thực tế từ 3 góc nhìn

Xin chào mọi người!

Tôi là một frontend engineer và đôi khi tự hỏi:

Tôi đang sử dụng AI… hay AI đang sử dụng tôi?

Khi cần thực hiện runtime validation trong TypeScript, Zod thường là một trong những lựa chọn đầu tiên mà chúng ta nghĩ đến.

Ngoài ra còn có những lựa chọn khác như ValibotArkType.

Tuy nhiên, hôm nay tôi muốn giới thiệu một cách tiếp cận hơi khác:

zod_vs_is-kit_new_2.gif

is-kit

is-kit là một toolkit nhẹ, không có dependency, dùng để xây dựng các TypeScript type guard có thể tái sử dụng.

Nó giúp bạn:

  • viết các hàm nhỏ theo kiểu isFoo;
  • kết hợp chúng thành những kiểm tra runtime phức tạp hơn;
  • giữ cho quá trình type narrowing diễn ra tự nhiên trong control flow thông thường của TypeScript.

is-kit an toàn ở runtime, có thể kết hợp linh hoạt và dễ sử dụng mà không yêu cầu bạn áp dụng một hệ thống validation dựa trên schema lớn.

Với is-kit, bạn có thể:

  • tạo và tái sử dụng các type guard có đầy đủ thông tin kiểu;
  • kết hợp guard bằng and, or, notoneOf;
  • kiểm tra cấu trúc object và collection;
  • parse hoặc assert các giá trị unknown mà không cần một schema framework lớn.

Trang tài liệu:

https://is-kit-docs.vercel.app/

Thoạt nhìn, is-kitZod có vẻ khá giống nhau.

Tuy nhiên, triết lý thiết kế của chúng thực tế rất khác:

  • is-kit → toolkit dùng để kết hợp các type guard;
  • Zod → thư viện dùng để định nghĩa schema và validate dữ liệu.

Vì vậy, thay vì hỏi:

Công cụ nào tốt hơn?

Hãy so sánh chúng từ góc nhìn thực tế:

Trong trường hợp nào thì mỗi công cụ sẽ dễ viết hơn?

Chúng ta sẽ xem xét ba khía cạnh:

  1. Lượng code;
  2. Khả năng đọc hiểu;
  3. Cách xử lý kiểu dữ liệu.

Cùng một ví dụ

Hãy thử validate một object User đơn giản.

is-kit

import {
  and,
  isInteger,
  isString,
  oneOfValues,
  optionalKey,
  predicateToRefine,
  safeParse,
  struct,
} from "is-kit";

const isPositiveInt = and(
  isInteger,
  predicateToRefine<number>((v) => v > 0),
);

const isUser = struct({
  id: isPositiveInt,
  name: isString,
  role: oneOfValues(["admin", "member"] as const),
  nickname: optionalKey(isString),
});

const result = safeParse(isUser, input);

if (result.valid) {
  result.value.role;
}

Zod

import { z } from "zod";

const UserSchema = z.object({
  id: z.number().int().positive(),
  name: z.string(),
  role: z.enum(["admin", "member"]),
  nickname: z.string().optional(),
});

const result = UserSchema.safeParse(input);

if (result.success) {
  result.data.role;
}

Ngay từ đây, chúng ta đã có thể thấy sự khác biệt chính:

  • is-kit → kết hợp nhiều guard nhỏ dựa trên function;
  • Zod → định nghĩa một schema theo cách declarative.

1. Lượng code

Code sử dụng Zod thường trông ngắn hơn.

Tại sao?

Bởi vì chúng ta có thể chain nhiều constraint tại cùng một vị trí:

z.string().min(1).max(20).regex(...)
z.number().int().positive()

Cách viết này hoạt động rất tốt với:

  • form;
  • validation dữ liệu API;
  • validation tại các input boundary của ứng dụng.

Mặt khác, is-kit phát huy lợi thế khi bạn muốn tái sử dụng cùng một type guard trong nhiều phần khác nhau của logic:

if (isUser(value)) {
  // value đã được thu hẹp thành User
}

const users = values.filter(isUser);

Bạn có thể truyền type guard trực tiếp vào control flow của TypeScript.

Khi sử dụng Zod, chúng ta thường gọi các method như safeParse, vì vậy nó giống với việc thực hiện một thao tác validation rõ ràng hơn.

Tóm tắt

  • Muốn tập trung toàn bộ validation rule tại một nơi → sử dụng Zod;
  • Muốn tái sử dụng type guard trong logic → sử dụng is-kit.

2. Khả năng đọc hiểu

Điều này phụ thuộc vào cách chúng ta định nghĩa thế nào là “dễ đọc”.

Zod

Tất cả các rule đều được tập trung tại một nơi:

const UserSchema = z.object({ ... });

Chúng ta có thể nhanh chóng hiểu được:

Cấu trúc dữ liệu nào được coi là hợp lệ?

Cách tiếp cận này rất phù hợp tại boundary của ứng dụng.

is-kit

is-kit gần với control flow thông thường của TypeScript hơn.

Nó cũng cho phép chúng ta kết hợp và thu hẹp type guard từng bước:

import { hasKey, narrowKeyTo } from "is-kit";

const hasRole = hasKey("role");

if (hasRole(value)) {
  value.role;
}

const byRole = narrowKeyTo(isUser, "role");
const isAdmin = byRole("admin");

if (isAdmin(value)) {
  value.role;
  value.name;
}

Trong ví dụ này, chúng ta có thể:

  1. thu hẹp toàn bộ cấu trúc bằng isUser;
  2. tập trung vào một property cụ thể là role;
  3. tiếp tục thu hẹp giá trị thành "admin".

Cách xử lý này phù hợp tự nhiên với quy trình:

Nhận một giá trị unknown → thu hẹp kiểu từng bước.

Tóm tắt

  • Muốn có một mô tả rõ ràng về dữ liệu hợp lệ → sử dụng Zod;
  • Muốn type narrowing hòa vào control flow một cách tự nhiên → sử dụng is-kit.

3. Kiểu dữ liệu được xử lý như thế nào?

Đây là nơi thể hiện sự khác biệt lớn nhất giữa hai cách tiếp cận.

Zod: Schema → Type

type User = z.infer<typeof UserSchema>;

Quy trình cơ bản là:

  1. định nghĩa schema;
  2. sinh type từ schema đó.

Cách tiếp cận này phù hợp khi bạn muốn:

Quản lý validation rule và TypeScript type tại cùng một nơi.

is-kit: Type guard → Type narrowing

is-kit chủ yếu tập trung vào control flow:

if (isUser(input)) {
  input.role;
}

TypeScript sẽ tự động thu hẹp kiểu của input.

Bạn cũng có thể lấy type tương ứng từ type guard:

import type { GuardedOf } from "is-kit";

type User = GuardedOf<typeof isUser>;

Tuy nhiên, lợi thế chính của is-kit không phải là việc sinh type.

Điểm quan trọng là:

TypeScript có thể hiểu logic của bạn trong quá trình các giá trị được thu hẹp kiểu.

Khác biệt cốt lõi

  • Zod → mạnh trong quy trình schema → type;
  • is-kit → mạnh trong quy trình type guard → type narrowing trong control flow.

Tổng kết so sánh

Góc nhìn Zod is-kit
Lượng code Phù hợp để định nghĩa schema tập trung Phù hợp để tái sử dụng type guard
Khả năng đọc hiểu Validation rule rõ ràng và tập trung Gần với control flow tự nhiên
Xử lý type Type được suy luận từ schema Type narrowing dựa trên control flow

Khi nào nên sử dụng từng công cụ?

Cách sử dụng cá nhân của tôi là:

Sử dụng Zod cho dữ liệu đầu vào bên ngoài

Ví dụ:

  • API response;
  • dữ liệu form;
  • environment variable.

Sử dụng is-kit cho logic bên trong ứng dụng

Ví dụ:

  • các nhánh điều kiện;
  • filter array;
  • kết hợp nhiều type guard;
  • thu hẹp giá trị unknown từng bước.

Chúng không phải là đối thủ trực tiếp

Hai công cụ này không nhất thiết phải cạnh tranh với nhau.

Trên thực tế, chúng có thể hoạt động rất tốt khi được sử dụng cùng nhau.

Một cách phân chia trách nhiệm rõ ràng là:

  • Zod → validate dữ liệu tại boundary của ứng dụng;
  • is-kit → tiếp tục thu hẹp và xử lý dữ liệu bên trong ứng dụng.

Kết luận

is-kit không phải là một phiên bản nhẹ hơn của Zod.

Nó tập trung vào một ý tưởng khác:

Biến type guard thành một phần tự nhiên trong TypeScript.

Trong khi đó, Zod vẫn là một công cụ rất mạnh cho:

Schema-first validation với type safety.

Vì vậy, câu hỏi thực sự không phải là:

Công cụ nào tốt hơn?

Mà là:

Bạn muốn đảm bảo type safety tại vị trí nào trong thiết kế hệ thống?

Nếu bạn quan tâm đến dự án, bạn có thể xem thêm tại đây:


Bài viết này là phiên bản tiếng Việt của bài viết tiếng Anh do chính tôi viết.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí