Короткое путешествие о том, что я узнал о проверке схемы Zod a TypeScript-first со статическим выводом типа.

С прошлой недели на выходных до этой недели (и на выходных тоже) в основном я узнаю о Зоде; в основном, это проверка схемы, которую можно запустить и во время выполнения, что в основном я использую TypeScript только для проверки типов в целях разработки, но, согласно документации, имя «схемы» в широком смысле относится к любому типу данных , от простого string до сложного вложенного объекта.

В приведенных выше заявлениях я также сказал вам, что Zod также проверит схему во время выполнения, а не только проверит тип объекта, массива и т. д. Самое интересное, что он выдаст нам ошибку, если мы не встретим схема, которую мы уже определили. Возьмем пример с string API от Zod.

Проверить неизвестные данные

Мы знаем, что если мы по-прежнему используем JavaScript в качестве основного кода, мы не можем убедиться, что параметры нашей функции нужны нам или нет. Так же, как и в TypeScript, если мы пишем функцию, которая принимает аргументы с типом unknown, нам нужно убедиться, что параметры или данные, которые мы будем обрабатывать в нашей функции.

Итак, в Zod мы можем убедиться в этом, просто написав две простые строки кода, подобные этой.

import { z } from 'zod'

function submitAddressUser(address: unknown) {
  const getAddress = z.string();
  getAddress.parse(address)

  return address
}

Мы можем видеть приведенный выше код, параметры, которые мы пишем, неизвестны — то есть мы предполагаем, что мы не знаем точно это значение, поэтому, если мы запустим эту функцию и дадим аргумент, что мы не хотим этого, как number

it('should run our app', () => {
  const testIt = submitAddressUser(123412)
  expect(testIt).toBe('cool address')
})

Если мы запустим его

Мы получим бросающую ошибку, потому что мы не даем правильный аргумент функции submitAddressUser это должно быть ошибкой во время выполнения, если мы запустим ее в браузере.

Простая история пользователя

Раньше мы уже писали простой Zod API, который string, но в этом API также есть много функций, которые мы можем использовать, например:

import { z } from 'zod'

function testTheApp(firstName: string) {
  const getTheFirstName = z.string().min(3);
  getTheFirstName.parse(firstName)

  return firstName
}

Если мы попытаемся ввести нашу форму, которая не соответствует этой схеме, мы должны получить ошибку.

it('should run our app', () => {
  const getMyFirstName = testTheApp("Ad")
  expect(getMyFirstName).toBe('Ad')
})

Вот ошибка, которую вернул Zod.

Это указывает на то, что мы получили проверку схемы нашей функции testTheApp с минимальным символом, равным трем.

Смешайте это с Rest API

Самое интересное в этой части, мы можем уловить это даже во время выполнения, давайте предположим, что мы вызываем Rest API с TypeScript и обычным fetch.

import { z } from 'zod'

async function getDetailTodo() {
  const fetchData = await fetch('https://jsonplaceholder.typicode.com/todos/1')
      .then(response => response.json())
      .then(json => json)

  console.log(fetchData)
}

Как вы знаете, в отличие от GraphQL, когда мы используем fetch, тогда данные из переменной fetchData должны стать неизвестными или какими-либо, которые мы не знаем на это ответ.

К счастью, с помощью Zod мы можем создать схему, которая должна возвращать то, что нам нужно, из этого ответа API.

import { z } from 'zod'

async function getDetailTodo() {
  const validateResponse = z.object({
    title: z.string()
  })
  
  const fetchData = await fetch('https://jsonplaceholder.typicode.com/todos/1')
      .then(response => response.json())
      .then(json => json)

  const data = validateResponse.parse(fetchData)
  return data
}

Для объяснения приведенного выше кода у нас есть переменная validateResponse для создания схемы, которая просто дает нам поле title из ответа этого API, если мы посмотрим на API, то увидим несколько полей из ответа.

{
  "userId": 1,
  "id": 1,
  "title": "delectus aut autem",
  "completed": false
}

Но в нашей схеме, которая validateResponse, мы просто хотим title отдельно от других полей, если мы попытаемся ее утешить

Zod также фильтрует остальные поля, которые не нужны в нашей схеме, но, если мы изменим схему, а затем допустим, что нам нужно поле subTitle

import { z } from 'zod'

async function getDetailTodo() {
  const validateResponse = z.object({
    title: z.string(),
    subTitle: z.string()
  })
  
  const fetchData = await fetch('https://jsonplaceholder.typicode.com/todos/1')
      .then(response => response.json())
      .then(json => json)

  const data = validateResponse.parse(fetchData)
  return data
}

Если мы запустим наше приложение

Мы видим, что Zod выдаст нам ошибку, которая заключается в том, что нам нужно изменить ответ этого API, чтобы убедиться, что наша схема соответствует ответу API.

Сделайте вывод о типе

Мы уже видим мощь Zod с API object, который может быть похож на проверку схемы, чтобы убедиться, что то, что нам нужно, это то, что у нас тоже есть. Но, вернемся к предыдущей теме, мы используем эту функцию

import { z } from 'zod'

async function getDetailTodo() {
  const validateResponse = z.object({
    title: z.string(),
  })
  
  const fetchData = await fetch('https://jsonplaceholder.typicode.com/todos/1')
      .then(response => response.json())
      .then(json => json)

  const data = validateResponse.parse(fetchData)
  return data
}

Как мы знаем, TypeScript будет делать вывод о возвращаемом типе функции на основе того, что мы возвращаем из нашей функции, но скажем, у нас есть случай, когда нам также нужен тип схемы и мы используем этот тип для других вспомогательных функций, например, мы можно использовать API от Zod, который называется infer

import { z } from 'zod'

const validateResponse = z.object({
  title: z.string(),
})

export type DetailTodoData = Promise<z.infer<typeof validateResponse>>

async function getDetailTodo(): DetailTodoData {
  const fetchData = await fetch('https://jsonplaceholder.typicode.com/todos/1')
      .then(response => response.json())
      .then(json => json)

  const data = validateResponse.parse(fetchData)
  return data
}

Множество

В Zod у нас также есть API, с которым мы можем взаимодействовать с типом данных Array — например, у нас есть эта функция

async function getListTodo() {
  const fetchData = await fetch('https://jsonplaceholder.typicode.com/todos')
      .then(response => response.json())
      .then(json => json)
  
  return fetchData
}

Если мы попытаемся использовать этот API, мы увидим много данных todos.

[
  {
    "userId": 1,
    "id": 1,
    "title": "delectus aut autem",
    "completed": false
  },
  {
    "userId": 1,
    "id": 2,
    "title": "quis ut nam facilis et officia qui",
    "completed": false
  },
  ...
]

Затем, скажем, в этом списке мы просто хотим показать пользователю поле title, мы также можем использовать API от Zod, который называется array

import { z } from 'zod'

async function getListTodo() {
  const validateTheResponse = z.array(z.object({
    title: z.string()
  }))
    
  const fetchData = await fetch('https://jsonplaceholder.typicode.com/todos')
      .then(response => response.json())
      .then(json => json)

  const data = validateTheResponse.parse(fetchData)
  return data
}

Если мы попытаемся увидеть результат нашей функции, она автоматически отфильтрует данные на основе схемы, которую мы создали выше.

Значение по умолчанию и дополнительная схема

Мы знаем, что в TypeScript мы также можем сделать наш тип необязательным, тогда внутри функции мы можем указать значение по умолчанию.

function generateHelloThere(name?: string) {
  const getName = name || 'There,'

  return `Hello ${getName}`
}

Предположим, у нас есть такой тестовый пример.

it('should return a hello name', () => {
  const welcomeMessage = generateHelloThere()
  expect(welcomeMessage).toBe('Hello There,')
})

Это должно дать нам значение по умолчанию

Но, в Zod, они уже предоставляют нам API, чтобы сделать значение по умолчанию и пометить его как необязательное.

import { z } from 'zod'

function generateHelloThere(name?: string) {
  const schemaName = z.string().optional().default('There,')
  const getName = schemaName.parse(name)

  return `Hello ${getName}`
}

Затем, после того, как мы запустим наш тестовый пример

Отсутствие этого API, нам нужно убедиться, что .optional() API должен сначала вызвать его перед .default('There,') API. default() API должен быть последним средством схемы, иначе наш тестовый пример выдаст нам ошибку

Минусы

После того, как мы рассказали об API, который предоставил Zod, у этого пакета есть и минусы, с моей точки зрения. Когда мы проверяем размер пакета этого пакета с сайта bundlejs

Он довольно большой, около 13 КБ, хотя он уже был в формате gzip.

Заключение

Зод, это полностью меняет правила игры, если нам нужно убедиться, что наш тип в нашей функции поймает его и во время выполнения, но, как мы упоминали выше, у этого пакета есть минусы, но если вы не думаете о размере пакета ваше приложение, я думаю, вы можете рассмотреть возможность отправки этого пакета, чтобы помочь вам как опекуну во время выполнения.

Помимо того, что мы пишем его на TypeScript, Zod также поможет вам в вашем приложении, если, с моей точки зрения, оно уже написано на JavaScript.

Кроме того, есть так много API, которые мы не упомянули здесь, вы можете сами взглянуть на документацию Zod.

https://github.com/colinhacks/zod#необязательно