Сохраняй надёжные типы на границах приложения, где DOM, сеть и JSON ускользают от компилятора.
Открыть этот урок в KodokonРеальный код начинается там, где заканчиваются идеальные типы: DOM, сеть и JSON отдают данные, о которых TypeScript ничего не знает. Первый рубеж - DOM. querySelector возвращает Element | null - слишком расплывчатый тип, чтобы прочитать value или вызвать focus().
const input = document.querySelector<HTMLInputElement>(
"#email"
);
if (input) {
input.value = "test@example.com";
input.focus();
}Параметр типа - это обещание, которое ты даёшь компилятору: попасть в нужный селектор - твоя ответственность. Когда элемент приходит из события, лучше использовать проверку instanceof, которая действительно проверяет тип во время выполнения.
const form = document.querySelector("form");
form?.addEventListener("submit", (event) => {
event.preventDefault();
if (event.target instanceof HTMLFormElement) {
console.log(new FormData(event.target));
}
});Второй рубеж - сеть. response.json() возвращает Promise<any>, то есть открытую дверь для чего угодно. Профессиональный минимум: объявить ожидаемый тип, проверить response.ok и вынести вызов в отдельную типизированную функцию.
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;
}Этот as Post остаётся заявлением о доверии: если API изменится, во время выполнения тебя никто не предупредит. Для критичных данных напиши защитник типа, который проверяет реальную форму JSON перед использованием.
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"
);
}