Xác thực với Loại bảo vệ và Loại đã ánh xạ


Lý Phương Nam
1 năm trước
Hữu ích 4 Chia sẻ Viết bình luận 0
Đã xem 7273

Phiên bản nâng cao của mã hiện đã có trên  NPM  và  GitHub .

Đã dành một phần đáng kể trong sự nghiệp lập trình của mình bằng các ngôn ngữ động, tôi hiểu giá trị của việc tạo mẫu nhanh và phản hồi mà họ cung cấp. Tôi cũng đã thấy đủ mã động để biết rằng hầu hết các cơ sở mã động có đầy đủ logic xác thực dễ vỡ mà anh em của chúng tôi từ trại được gõ tĩnh không phải đối phó. Chà, điều đó không hoàn toàn đúng. Họ vẫn phải đối phó với nó, nhưng tôi nghĩ họ có thời gian dễ dàng hơn vì trình biên dịch có thể giúp họ. Có nhiều giải pháp để giải quyết vấn đề này trong trại động dưới dạng thư viện và DSL, nhưng hôm nay tôi sẽ trình bày một giải pháp không sử dụng gì ngoài các khả năng tích hợp của TypeScript để giúp chúng tôi xây dựng trình xác nhận cho POJO (cũ đơn giản Đối tượng JavaScript).

Các thành phần của giải pháp khá đơn giản, nhưng khi kết hợp lại với nhau, chúng ta có được một cách hay để xác thực các đối tượng JavaScript cũ đơn giản được tuần tự hóa và gửi qua dây dưới dạng JSON.

Thành phần đầu tiên là các loại bản đồ. Đây là những loại phụ thuộc vào loại theo một cách nào đó. Chúng tôi không có sự phụ thuộc hoàn toàn như trong phép tính lambda được gõ theo thứ tự cao hơn, nhưng nó đủ gần và hoạt động thực sự tốt cho các loại giao diện hiển thị trong JavaScript. Chúng tôi sẽ sử dụng những điều này để "nghiêm ngặt hóa" định nghĩa loại của tải trọng JSON. Loại tải trọng sẽ có các trường nullable để trình biên dịch sẽ khiếu nại nếu chúng ta vô tình sử dụng loại trong mã của chúng tôi mà không kiểm tra trước các trường cho các giá trị null. Có lẽ dễ dàng hơn để hiển thị với một ví dụ:

type Payload = {
  f1?: string,
  f2?: number,
  f3?: {
    f1?: string,
    f2?: string[]
  }
};


Giả sử đây là cấu trúc của tải trọng, chúng ta sẽ cần chuyển đổi nó thành phiên bản chặt chẽ hơn bằng cách loại bỏ các trường không thể. Đây là nơi các loại bản đồ đến để giải cứu. Chúng ta có thể tạo một loại chung cho các loại "nghiêm ngặt" như loại trên với loại ánh xạ chung sau:

type Strict<T> = {
  [K in keyof T]-?: Exclude<Strict<T[K]>, undefined>
};


Điều đang diễn ra ở đây là chúng tôi đang loại bỏ đệ quy các trường không thể và thay thế chúng bằng các phiên bản nghiêm ngặt của chúng bằng Exclude(được định nghĩa trong thư viện chuẩn của TypeScript). Áp dụng loại chung trên cho loại tải trọng của chúng tôi sẽ cung cấp cho chúng tôi như sau:

type StrictPayload = Strict<Payload>;
// type StrictPayload = {
//   f1: string;
//   f2: number;
//   f3: Strict<{
//     f1?: string | undefined;
//     f2?: string[] | undefined;
//   }>;
// }


Lưu ý cấu trúc đệ quy cho trường cuối cùng. Tôi khuyên bạn nên thử nghiệm Stricttrong sân chơi TypeScript để có cảm giác tốt hơn về nó. Hãy thử thay thế kiểu generic với string, string[], number, boolean, và loại nào khác bạn có thể nghĩ đến.

Vậy tại sao chúng ta cần loại này? Chúng tôi cần nó bởi vì chức năng xác thực của chúng tôi sẽ trả về phiên bản "nghiêm ngặt" của bất cứ điều gì chúng tôi vượt qua. Cụ thể hơn, chữ ký của chức năng xác thực của chúng tôi sẽ là:

function validator<T>(i: unknown, validation: Validated<T>): i is Strict<T> {
  // ...
}


Vì vậy, hai phần còn thiếu là phần thân của hàm (nó thực sự là một kiểu bảo vệ) và định nghĩa của Validated. Chúng ta cần một thành phần bổ sung để xác định Validatedvà đó là loại có điều kiện.

Các định dạng cho các loại điều kiện là R extends S ? T : U. Trong tiếng Anh đơn giản, điều đó nói rằng nếu Rđược gán cho S, thì loại kết quả là Tvà nếu đó không phải là trường hợp, thì loại kết quả là U. Phải mất một thời gian tôi mới hiểu được giá trị của những thứ này, nhưng nếu bạn nhìn vào định nghĩa của Excludenó bằng cách sử dụng các loại có điều kiện thì chúng rất tiện dụng.

Bây giờ chúng ta có thể định nghĩa Validated:

type Validated<T> =
  T extends symbol | boolean | string | number ? boolean :
    { [K in keyof T]-?: Validated<T[K]> };


Một lần nữa có lẽ dễ sử dụng hơn. Nếu chúng tôi áp dụng điều này cho loại tải trọng của mình, thì chúng tôi sẽ nhận được như sau:

type ValidatedPayload = Validated<Payload>;
// type ValidatedPayload = {
//    f1: boolean;
//    f2: boolean;
//    f3: // ...
// };


Chơi với nó trong sân chơi TypeScript để hiểu chuyện gì đang xảy ra. Những gì chúng tôi đang cố gắng làm là gán các giá trị boolean cho tất cả các trường. Bạn có thể đã thấy nơi này sẽ đi. Bằng cách chuyển tải trọng xác thực cho chức năng xác thực, chúng tôi có thể xác minh rằng tất cả các trường là thực sự đúng. Nếu một trong các trường không đúng, thì điều đó có nghĩa là trường đó không được xác thực và chúng ta có thể xử lý theo đó bằng cách ghi lại cảnh báo hoặc ném ngoại lệ để tạm dừng thực thi. Về cơ bản tôi chỉ mô tả chức năng xác nhận sẽ trông như thế nào, vì vậy hãy điền nó vào:

function allTrue(validation: any) {
  if (typeof validation === "boolean") {
    return validation;
  }
  for (const k of Object.keys(validation)) {
    const value = validation[k];
    if (!allTrue(value)) return false;
  }
  return true;
}

function validator<T>(i: unknown, validation: Validated<T>): i is Strict<T> {
  return allTrue(validation);
}


Nó chỉ là một bản dịch của những gì tôi nói. Chúng tôi đệ quy xuống từng đối tượng và xác minh rằng mỗi trường được đặt thành một giá trị thực và nếu chúng tôi chạy vào một giá trị không đúng, thì chúng tôi trả về false để kiểm tra bảo vệ kiểu không thành công. Hãy thử chạy trình xác nhận trong sân chơi TypeScript với ví dụ về tải trọng để xem điều gì xảy ra:

const payload = JSON.parse(JSON.stringify({}));
const validation = {
  f1: !!payload.f1,
  f2: !!payload.f2,
  f3: {
    f1: !!(payload.f3 || {}).f1,
    f2: [!!(payload.f3 || {}).f2] // This could have more than 1 element
  }
};
if (validator<Payload>(payload, validation)) {
  console.log('Received valid payload', payload);
} else {
  console.error('Invalid payload', payload);
}


Đây rõ ràng không phải là kết thúc của câu chuyện bởi vì tất cả những gì chúng tôi đang làm là xác minh rằng các lĩnh vực có mặt. Có một phần mở rộng rõ ràng của phương pháp này cũng sẽ kiểm tra các loại. Xem nếu bạn có thể tìm ra làm thế nào để làm điều đó. Đây là một gợi ý: nó liên quan đến việc sửa đổi Validatedđể trả về loại giá trị nguyên thủy, thay vì chỉ chuyển đổi mọi thứ thành giá trị boolean.

Hữu ích 4 Chia sẻ Viết bình luận 0
Đã xem 7273