Когда вы работаете с интерфейсом FormData в JavaScript, где данные добавляются в виде пар ключ/значение, нет встроенного способа обеспечить безопасность типов для ключей, которые вы добавляете. Это может привести к опечаткам, отсутствующим ключам и неожиданным ошибкам во время выполнения. Но в TypeScript мы можем решить эту проблему, обеспечивая строгую проверку ключей.

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

После этого я искал способ гарантировать, что TypeScript не позволит этих ошибок.

В этой статье я покажу вам, как сделать FormData ключи безопасными по типу с помощью TypeScript.

Предварительные требования

Чтобы извлечь максимальную пользу из этой статьи, вы должны иметь базовое понимание следующего:

  1. Программирование на JavaScript

  2. Основы TypeScript, особенно как работают интерфейсы, типы и оператор keyof

  3. интерфейс FormData

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

Шаг 1: Определите допустимые ключи

Старый способ

Обычно данные добавляются в FormData вручную, с использованием простых строк:

const payload = new FormData();

payload.append("id", "1122");
payload.append("name", "Clark Kent");

payload.append("agge", "36"); // Опечатка в ключе допустима

В приведенном выше фрагменте кода видно, что была опечатка при определении ключа для age. Но TypeScript не отметит это как ошибку, и это может привести к ошибкам при отправке этих данных с API-запросом.

Лучший способ

Вместо того чтобы вручную вводить ключи, определите их в схеме объекта с помощью интерфейса TypeScript.

interface MyAllowedData {
    id: number;
    name: string;
    age: number;
}

В качестве альтернативы вы можете определить их с помощью типов:

type MyAllowedData = {
    id: number;
    name: string;
    age: number;
}

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

Далее определите объединяющий тип из каждого ключа в вашем интерфейсе.

type MyFormDataKeys = keyof MyAllowedData
// это то же самое, что `type MyFormDataKeys = 'id' | 'name' | 'age'`

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

Шаг 2: Создание вспомогательной функции Append

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

function appendToFormData (formData: FormData, key: MyFormDataKeys, value: string) {
  formData.append(key, value);
};

Функция appendToFormData принимает три аргумента. Вот как это все работает:

  • Первый аргумент, formData, является экземпляром объекта FormData. Здесь будут добавляться пары ключ/значение перед отправкой их в запрос API.

  • Второй аргумент, key, это имя ключа поля, которое вы хотите добавить. Его тип – MyFormDataKeys, объединенный тип, который мы создали, чтобы гарантировать, что к FormData добавляются только те ключи, которые мы определили.

  • Третий аргумент – строка value, которая представляет собой значение, которое должно быть добавлено с ключом.

Обратите внимание, что FormData принимает только строки и Blob типы в качестве значений в каждой паре ключ/значение. В этом руководстве мы работаем только со строковыми значениями – но имейте в виду, что вы можете использовать значения blob для добавления файлов в запросы к API.

Теперь давайте протестируем функцию:

const payload = new FormData();

appendToFormData(payload, "id", "19282"); // ✅ Разрешено
appendToFormData(payload, "name", "Lenny Brown"); // ✅ Разрешено
appendToFormData(payload, "age", "20"); // ✅ Разрешено

appendToFormData(payload, "someOtherKey", "89"); // ❌ Ошибка TypeScript: Аргумент типа 'someOtherKey' не может быть присвоен.

Шаг 3: Используйте вспомогательную функцию после отправки формы

Теперь давайте добавим наши поля в FormData перед отправкой их на API.

const handleSubmitForm = () => {
  const payload = new FormData();
   appendToFormData(payload, "id", "19282");
   appendToFormData(payload, "name", "Lenny Brown");
   appendToFormData(payload, "age", "20");

  // Отправить нагрузку через API
  fetch("/api/submit", { method: "POST", body: payload });
};

Добавление полей из объекта

В качестве альтернативы, если у вас уже есть вся нагрузка в объекте, вы можете избежать добавления каждого поля по отдельности, реализовав функцию следующим образом:

const handleSubmitForm = () => {
  // все ваши поля в объекте
  const formValues: MyAllowedData = {
    id: 1123,
    name: 'John Doe',
    age: 56
  }
  const payload = new FormData();

  Object.entries(formValues).forEach(([key, value]) => {
    appendToFormData(payload, key as MyFormDataKeys, `${value}`); // используйте шаблонные литералы для передачи значения
  });

  // Отправить нагрузку через API
  fetch("/api/submit", { method: "POST", body: payload });
};

В приведенном фрагменте мы используем Object.entries, чтобы перебрать каждую пару ключ/значение в объекте, чтобы добавить их в объект FormData. Обратите внимание, что значение в каждой паре, будь то строка или число, передается как строка, используя шаблонные литералы, чтобы предотвратить несоответствие типов TypeScript из-за аргумента value в нашей вспомогательной функции.

Заключение

Используя оператор keyof TypeScript, мы можем сделать FormData.append() полностью безопасным с точки зрения типов. Эта простая техника помогает предотвратить несоответствия ключей и делает ваши API-запросы более надежными.

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

Спасибо за чтение!