ФронтендTypeScriptJavaScript

Первый проект на TypeScript: превращаем JavaScript-приложение в код с типами

Переносим небольшой список задач с JavaScript на TypeScript, описываем данные, проверяем DOM, включаем strict и ловим ошибку до запуска в браузере.

Кодик

Автор

6 мин чтения

TypeScript не заменяет JavaScript, а проверяет отношения между данными до запуска программы. Перенесём знакомый список задач в .ts, опишем объект Task, включим strict и увидим, как компилятор останавливает строку вместо boolean ещё до открытия браузера.

Изучать типы на переменных name: string скучно, потому что непонятно, какую ошибку они предотвращают. В мини-проекте связь видна сразу. Задача имеет id, title и done. Функция addTask должна принимать строку, toggleTask работает с числовым id, а render получает массив Task. Если перепутать поля, компилятор покажет строку.

1Описываем

type Task фиксирует форму одной задачи и назначение каждого поля.

2Проверяем

strict и noEmitOnError запрещают сборку, когда отношения типов нарушены.

3Запускаем

tsc создаёт dist/main.js, а браузер исполняет обычный JavaScript.

Что TypeScript добавляет к JavaScript

Проект содержит index.html, src/main.ts и tsconfig.json. DOM-элементы ищутся через querySelector с generic-параметром, но результат всё равно может быть null. Функция must проверяет это во время выполнения и после throw сужает тип до T. Массив tasks явно хранит Task.

ЭтапЧто происходитПризнак результата
ИсходникРазработчик пишет main.ts и тип TaskРедактор знает поля
Проверкаtsc анализирует присваивания и вызовыОшибка имеет файл и строку
СборкаТипы удаляются, создаётся dist/main.jsБраузер получает JavaScript
ВыполнениеDOM и пользовательский ввод работают обычноСписок реагирует на события

У проекта есть один главный маршрут: получить входные данные, проверить их, выполнить действие и показать результат. Если каждый этап можно проверить отдельно, ошибка перестаёт быть загадкой.

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

TypeScript полезнее после базового JavaScript. Типы не объясняют циклы, функции, DOM и события. Если эти основы пока не знакомы, сначала соберите маленький JS-проект, затем перенесите его и сравните ошибки.

Переносим список задач в main.ts

Создайте каталог, выполните npm init -y и npm install -D typescript. Положите main.ts в src, настройте tsconfig.json и запустите npx tsc. В index.html подключите ./dist/main.js с type="module". Для автоматической пересборки используйте npx tsc --watch. Не подключайте .ts напрямую в обычный браузер.

type Task = {
  id: number;
  title: string;
  done: boolean;
};

function must<T extends Element>(value: T | null, name: string): T {
  if (!value) throw new Error("Не найден элемент: " + name);
  return value;
}

const form = must(document.querySelector<HTMLFormElement>("#task-form"), "form");
const input = must(document.querySelector<HTMLInputElement>("#task-title"), "input");
const list = must(document.querySelector<HTMLUListElement>("#task-list"), "list");
const tasks: Task[] = [];

function addTask(title: string): void {
  tasks.push({ id: Date.now(), title, done: false });
  render();
}

function render(): void {
  list.replaceChildren(...tasks.map((task) => {
    const item = document.createElement("li");
    item.textContent = (task.done ? "✓" : "○") + " " + task.title;
    item.addEventListener("click", () => {
      task.done = !task.done;
      render();
    });
    return item;
  }));
}

form.addEventListener("submit", (event) => {
  event.preventDefault();
  const title = input.value.trim();
  if (title) addTask(title);
  form.reset();
});
Что должно произойти после запуска
  • npx tsc завершается без ошибок и создаёт dist/main.js.
  • Форма добавляет Task с числовым id, строковым title и boolean done.
  • Клик по li переключает done, а render обновляет значок.
  • Присваивание task.done = "да" останавливает сборку с диагностикой типа.

Большая часть типов выводится автоматически. Мы явно описываем форму данных и важные границы, но не пишем аннотацию у каждой локальной переменной. Так код остаётся читаемым, а компилятор получает достаточно информации.

any и проверяемые границы
Что отличает устойчивый проект от случайного успеха. Аннотации нужны там, где связывают части программы

Как компилятор выводит типы и сужает null

TypeScript строит модель программы до запуска. Когда tasks объявлен как Task[], push обязан получить объект с нужными полями. Когда task.done имеет boolean, оператор ! возвращает boolean, а строка запрещена. Generic в querySelector сообщает ожидаемый класс элемента, но не отменяет null: DOM может не содержать селектор.

ЧастьОтветственностьЧто проверить
type TaskФиксирует форму доменных данныхВсе поля имеют назначение
InferenceВыводит тип локальных значенийНет лишних аннотаций
GenericСвязывает querySelector с видом элементаМетоды input доступны редактору
NarrowingУбирает null после явной проверкиDOM-ошибка остаётся честной
tsconfigЗадаёт strict и границы сборкиОшибка блокирует emit

Типы существуют только во время разработки. Если localStorage, fetch или пользовательская форма возвращают неожиданные данные, оператор as Task не проверит объект. Нужна runtime-валидация или собственная функция-предикат. Избегайте any: он отключает важную часть проверок и распространяется дальше по коду.

as не превращает значение в правильный объект. Type assertion сообщает компилятору ваше предположение и исчезает из JavaScript. Для JSON из сети проверяйте поля во время выполнения.

Создаём ошибку и читаем диагностику tsc

Соберите проект и добавьте две задачи. Затем намеренно замените done: false на done: "нет" и прочитайте диагностику tsc. Верните boolean. Удалите #task-list из HTML и запустите страницу: must должен дать понятную runtime-ошибку. После этого добавьте функцию toggleTask(id: number): void и перенесите переключение из render. Попробуйте вызвать её строкой.

Проверка своими руками
  1. Соберите проект в dist и откройте список задач.
  2. Создайте ошибку string вместо boolean и найдите строку диагностики.
  3. Удалите DOM-элемент и проверьте runtime-защиту must.
  4. Вынесите переключение в typed-функцию toggleTask.
  5. Добавьте union type для трёх фильтров.
  6. Загрузите JSON из localStorage и проверьте его поля до as.
Готово, если выполняются все пункты
  • tsc проходит только для согласованного кода.
  • DOM null обрабатывается явно.
  • Task используется во всех функциях списка.
  • Неверный фильтр ловится до запуска.

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

Четыре проверки первого TS-проекта
Четыре проверки перед следующим шагом. Каждая проверка предотвращает конкретный класс ошибки

Добавляем типы там, где они окупаются

Дальше вынесите Task и Filter в types.ts, добавьте модуль хранения и тест чистых функций. Для сетевых данных подключите схему runtime-валидации или напишите type guard. Не начинайте с сложных generics и utility types: сначала получите пользу от объектов, union, параметров и возвращаемых значений.

Как изучить тему в Кодике

В Кодике сначала повторите объекты, массивы, функции и DOM в JavaScript. Затем перенесите один небольшой файл в TypeScript и включите strict. Каждую новую конструкцию связывайте с конкретной ошибкой проекта.

ШагЧто изучить в КодикеМини-проверка
1Объекты и массивы JavaScriptСобрать tasks без типов
2Object type и функцииОписать Task и addTask
3DOM null и narrowingНаписать must без non-null assertion
4Union и literal typesОграничить фильтр тремя значениями
5Runtime-границыПроверить JSON из localStorage

Не пытайтесь запомнить весь API. В тренажёре Кодика сначала воспроизведите чистую логику без библиотеки, затем восстановите подключение и обработку ошибок. Финальный тест: объяснить проект по памяти и добавить одну свою функцию.

Везде используется any

any отключает проверки. Начните с unknown на внешней границе и сузьте значение условиями.

После querySelector ставят !

Non-null assertion скрывает риск. Проверьте элемент и дайте понятную ошибку.

.ts подключают прямо в браузер

Сначала запустите tsc или сборщик и подключайте созданный JavaScript.

as считается валидацией

Assertion не меняет данные во время выполнения. Проверяйте JSON отдельно.

Сверьтесь с первичным источником. Команды, версии и ограничения примера проверяйте по официальному TypeScript Handbook. Если интерфейс изменился, первичная документация важнее старого скриншота.

Что получится в итоге

Готовый результат: Первый проект на TypeScript: превращаем JavaScript-приложение в код с типами
Один TypeScript-файл описывает Task, безопасно находит DOM и не выпускает сборку, если boolean, id или фильтр получили неверный тип.

Короткие ответы
TypeScript выполняется в браузере?

Нет. Компилятор проверяет .ts и создаёт обычный JavaScript, из которого типы удалены.

Почему querySelector всё равно возвращает null?

Тип элемента известен, но наличие узла зависит от HTML во время выполнения.

Чем unknown лучше any для внешнего JSON?

unknown требует проверить значение перед использованием, а any отключает проверки.

Как понять, что проект действительно работает?

Проверьте основной сценарий, неверный ввод, повторный запуск и один граничный случай. Затем объясните путь данных своими словами.

Можно ли начать с готового кода из статьи?

Да. Добейтесь результата, измените одно правило и соберите ключевой файл заново без копирования.

Переносите знакомый JavaScript, а не учите типы в пустоте

Сначала закрепите проектную основу в курсе JavaScript. Возьмите знакомый Todo List или калькулятор и перенесите оди

Ошибки DOM сравните со статьёй про null. В Кодике отдельно перепишите тип Task, must и union фильтра по памяти.