Kodokon kodokon.com

Типизация реального кода: DOM, fetch, JSON

Сохраняй надёжные типы на границах приложения, где DOM, сеть и JSON ускользают от компилятора.

10 мин · 3 вопросов

Открыть этот урок в Kodokon

Реальный код начинается там, где заканчиваются идеальные типы: DOM, сеть и JSON отдают данные, о которых TypeScript ничего не знает. Первый рубеж - DOM. querySelector возвращает Element | null - слишком расплывчатый тип, чтобы прочитать value или вызвать focus().

TYPESCRIPT
const input = document.querySelector<HTMLInputElement>(
  "#email"
);

if (input) {
  input.value = "test@example.com";
  input.focus();
}
Параметр типа у querySelector и проверка на null

Параметр типа - это обещание, которое ты даёшь компилятору: попасть в нужный селектор - твоя ответственность. Когда элемент приходит из события, лучше использовать проверку instanceof, которая действительно проверяет тип во время выполнения.

TYPESCRIPT
const form = document.querySelector("form");

form?.addEventListener("submit", (event) => {
  event.preventDefault();
  if (event.target instanceof HTMLFormElement) {
    console.log(new FormData(event.target));
  }
});
instanceof одновременно проверяет и сужает тип

Второй рубеж - сеть. response.json() возвращает Promise<any>, то есть открытую дверь для чего угодно. Профессиональный минимум: объявить ожидаемый тип, проверить response.ok и вынести вызов в отдельную типизированную функцию.

TYPESCRIPT
interface Post {
  id: number;
  title: string;
}

async function getPost(id: number): Promise<Post> {
  const url = `https://api.example.com/posts/${id}`;
  const response = await fetch(url);
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}`);
  }
  return (await response.json()) as Post;
}
Централизованный типизированный вызов fetch

Этот as Post остаётся заявлением о доверии: если API изменится, во время выполнения тебя никто не предупредит. Для критичных данных напиши защитник типа, который проверяет реальную форму JSON перед использованием.

TYPESCRIPT
function isPost(value: unknown): value is Post {
  if (typeof value !== "object" || value === null) {
    return false;
  }
  const obj = value as Record<string, unknown>;
  return (
    typeof obj.id === "number" &&
    typeof obj.title === "string"
  );
}
Защитник типа, проверяющий полученный JSON (продолжение примера)

Проверка знаний

Убедись, что запомнил ключевые моменты этого урока.

  1. Какой тип возвращает response.json() у ответа fetch?
    • Promise<unknown>
    • Promise<any>
    • Promise<object>
    • any
  2. Почему event.target instanceof HTMLFormElement лучше утверждения as HTMLFormElement?
    • Это быстрее во время выполнения
    • instanceof действительно проверяет тип во время выполнения, тогда как as лишь заставляет компилятор замолчать
    • as запрещён в строгом режиме
  3. Что возвращает document.querySelector('#email') без параметра типа?
    • HTMLInputElement
    • Element | null
    • HTMLElement
    • null