Перейти к основному содержимому

Рекомендации по разработке на TypeScript

Разработчику Архитектору

Дальше: Форматы и подключение · Обработка ошибок · Справочник — лучшие практики

Эта статья раскрывает Справочник — лучшие практики в формате правил команды: strict, слои типов, миграция, CI.


TypeScript даёт максимум пользы, когда команда договорилась о правилах: strict-флаги, работа с внешними данными, слои типов, CI и постепенная миграция с JavaScript. Здесь — инженерные практики; синтаксис — в 10 и 14.


Базовые правила

  1. Включайте strict: true в новых проектах.
  2. Минимизируйте any; для неизвестных данных — unknown + проверка.
  3. Проектируйте типы до реализации для API и доменных сущностей.
  4. Разделяйте DTO (сеть) и domain (бизнес-логика).
  5. Гоняйте tsc --noEmit в CI на каждый PR.

Рекомендуемый tsconfig для нового проекта

{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"noUncheckedIndexedAccess": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"forceConsistentCasingInFileNames": true,
"useUnknownInCatchVariables": true,
"skipLibCheck": true
}
}
ФлагНазначение
strictБазовый набор строгих проверок
noUncheckedIndexedAccessarr[i]T | undefined
useUnknownInCatchVariablescatch (e: unknown)
skipLibCheckБыстрее сборка; не проверять .d.ts в node_modules

Флаг noUncheckedIndexedAccess часто даёт первую волну ошибок в legacy-коде: arr[i] становится T | undefined, и это нормально при переходе на strict. useUnknownInCatchVariables убирает неявный any в catch (e) — сочетается с обработкой ошибок.

Полная таблица — Справочник — флаги безопасности.

Legacy-проекты

В старом коде включайте strict по флагам или по папкам. В новом коде — сразу полный strict, иначе типовой долг растёт быстрее, чем сокращается.


any vs unknown vs явные типы

anyunknownЯвный тип
Проверка компилятораОтключенаНужен narrowingПолная
Внешний JSONПлохоХорошоПосле валидации
Временный костыльДопустимо с TODOЛучше
function handlePayload(raw: unknown): User {
if (typeof raw !== "object" || raw === null) {
throw new Error("Invalid payload");
}
const o = raw as Record<string, unknown>;
if (typeof o.id !== "string" || typeof o.name !== "string") {
throw new Error("Invalid user");
}
return { id: o.id, name: o.name };
}

Валидация в production

Ручные проверки масштабируются плохо. Часто используют Zod или Valibot: схема → parse → тип:

import { z } from "zod";

const UserSchema = z.object({
id: z.string(),
name: z.string(),
});

type User = z.infer<typeof UserSchema>;

function parseUser(raw: unknown): User {
return UserSchema.parse(raw);
}

Разбор:

  • z.infer связывает runtime-схему и TypeScript-тип.
  • При ошибке валидации бросайте понятное исключение вместо тихого undefined.

Структура типов в репозитории

src/
types/
api.ts # DTO запросов/ответов
domain.ts # сущности предметной области
mappers/
user.ts # api → domain
services/
controllers/
СлойСодержимое
types/api.tsТо, что приходит по HTTP
types/domain.tsПравила бизнеса
mappers/Явное преобразование DTO → domain, без скрытой логики в UI

Не смешивайте DTO React-компонента и ответ API в одном interface — при смене API сломается UI без явной ошибки в маппере.


Type-driven development

  1. Опишите union состояний (загрузка / успех / ошибка).
  2. Опишите сигнатуры сервисов и handlers.
  3. Реализуйте код — компилятор подскажет пропущенные ветки.

Пример состояния UI — в типы §discriminated union и Асинхронность.


Миграция с JavaScript

TypeScript рассчитан на постепенный переход:

ШагДействие
1npm i -D typescript, npx tsc --init
2"allowJs": true — TS видит .js
3"checkJs": true + JSDoc @param в .js (опционально)
4Переименование .js.ts по файлу
5npm i -D @types/* для библиотек
6Включение strict по мере исправлений
{
"compilerOptions": {
"allowJs": true,
"checkJs": false,
"strict": false
}
}

После стабилизации — strict: true и отключение allowJs для переписанных папок.

Краткий указатель в JS-курсе — JS-курс: поэтапная миграция.


Антипаттерны

АнтипаттернПочему плохо
Откладывать типы на потомДолг растёт быстрее, чем сокращается
@ts-ignore без комментарияСкрывает реальные баги
as User на response.json()Ложная уверенность
Один огромный types.tsСложно ревьюить и искать
Отключить strict в CIРегрессии проходят незамеченными

Допустимый as — после проверки или в тестовых заглушках с пояснением.


CI и локальный workflow

package.json:

{
"scripts": {
"typecheck": "tsc --noEmit",
"build": "tsc",
"lint": "eslint . --ext .ts,.tsx"
}
}

В CI минимум:

npm ci
npm run typecheck
npm test

Разделите typecheck и bundle: Vite может собрать проект даже при ошибках, если tsc не в цепочке build.


Чек-лист code review

  • Нет нового any без комментария с причиной и сроком исправления.
  • Публичные экспорты типизированы (параметры и возврат).
  • catch использует unknown.
  • Union-ветки покрыты или есть exhaustive never в default.
  • Внешние данные проходят parse/validate, не as.
  • Нет дублирования DTO и domain без маппера.

Практика

  1. Включите strict в учебном проекте и исправьте все ошибки в одном модуле.
  2. Вынесите типы API в types/api.ts и domain в types/domain.ts.
  3. Добавьте npm run typecheck в CI или pre-commit.
  4. Перепишите один .js-файл в .ts с allowJs: true.
  5. Замените один any на unknown с функцией-сужением.

Смежные статьи