TypeScript Pro: Глубокое погружение в систему типов

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

1. Продвинутые дженерики, ограничения типов и сопоставляемые типы (Mapped Types)

Продвинутые дженерики, ограничения типов и сопоставляемые типы (Mapped Types)

Добро пожаловать в курс TypeScript Pro: Глубокое погружение в систему типов. Мы начинаем наше путешествие с фундаментальных, но часто неправильно понимаемых инструментов, которые превращают TypeScript из простого линтера в мощный инструмент архитектуры программного обеспечения.

Многие разработчики останавливаются на базовом понимании дженериков, используя их как «заглушки» для типов. Однако истинная сила TypeScript раскрывается, когда мы начинаем ограничивать эти дженерики и трансформировать их с помощью сопоставляемых типов (Mapped Types). В этой статье мы разберем, как создавать гибкие, но безопасные абстракции.

Дженерики: за пределами <T>

Дженерики (Generics) часто сравнивают с аргументами функций, но для типов. Если функция принимает значения и возвращает значение, то дженерик принимает тип и возвращает новый тип.

Рассмотрим классический пример функции идентификации, но взглянем на него глубже:

Здесь T — это переменная типа. Когда мы вызываем identity("Hello"), TypeScript подставляет string вместо T. Это называется выводом типов (type inference).

Дженерики по умолчанию

Иногда нам нужно, чтобы дженерик имел тип «по умолчанию», если разработчик не указал его явно и TypeScript не смог его вывести. Это похоже на параметры по умолчанию в функциях ES6.

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

Ограничения дженериков (Generic Constraints)

Самая большая проблема с «голым» дженериком <T> заключается в том, что TypeScript ничего о нем не знает. Для компилятора T — это абсолютно любой тип. Это значит, что вы не можете безопасно обращаться к свойствам этого типа.

Попробуем написать функцию, которая возвращает длину элемента:

TypeScript прав: а что, если T будет числом (number)? У чисел нет свойства .length. Чтобы исправить это, мы должны ограничить дженерик. Мы говорим компилятору: «Я не знаю точно, что это за тип, но я обещаю, что у него точно будет свойство length».

Для этого используется ключевое слово extends:

Теперь мы можем передавать строки, массивы или объекты с полем length, но не числа или булевы значения.

!Визуализация принципа работы ограничений типов (Constraints), где extends работает как фильтр.

Использование параметров типа в ограничениях

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

Представьте функцию, которая безопасно получает значение свойства из объекта:

Здесь K extends keyof T означает, что K может быть только одним из ключей объекта T. Если T — это { x: number, y: number }, то K может быть только "x" или "y".

Сопоставляемые типы (Mapped Types)

Если дженерики — это функции для типов, то Mapped Types — это циклы для типов. Они позволяют создавать новые типы, перебирая ключи существующего типа.

Синтаксис напоминает цикл for...in в JavaScript, но внутри квадратных скобок определения типа.

Анатомия Mapped Type

Разберем по частям:

  • keyof T: получает объединение всех ключей типа T (например, "name" | "age").
  • P in ...: это итератор. P будет последовательно принимать значение каждого ключа.
  • T[P]: это доступ к типу значения по ключу P (lookup type).
  • В таком виде MappedType<T> просто создает копию типа T. Но мощь заключается в трансформации.

    Пример: Readonly и Partial

    В TypeScript есть встроенные утилиты Readonly<T> и Partial<T>. Давайте посмотрим, как они реализованы внутри, используя Mapped Types.

    Создание Partial (все поля необязательные):

    Знак вопроса ? делает каждое поле необязательным.

    Создание Readonly (все поля только для чтения):

    Ключевое слово readonly добавляет модификатор неизменяемости к каждому полю.

    Модификаторы сопоставления (+ и -)

    Иногда нам нужно не добавить модификатор, а удалить его. Например, у вас есть тип, где все поля readonly и опциональны, а вы хотите сделать их изменяемыми и обязательными (например, для формы редактирования).

    Для удаления модификаторов используется префикс -.

    * -?: удаляет опциональность (делает поле обязательным). * -readonly: удаляет флаг только для чтения.

    Пример утилиты MutableRequired:

    Переименование ключей с помощью as

    Начиная с TypeScript 4.1, мы можем не только менять типы значений, но и переименовывать сами ключи, используя конструкцию as и шаблонные литералы (Template Literal Types).

    Допустим, мы хотим создать геттеры для всех полей объекта:

    typescript interface SignUpForm { email: string; password: string; age: number; }

    // Мы хотим создать тип, где каждое поле соответствует полю формы, // но содержит либо сообщение об ошибке (string), либо null. type ValidationErrors<T> = { [P in keyof T]?: string | null; };

    const errors: ValidationErrors<SignUpForm> = { email: "Invalid email format", // password и age могут отсутствовать или быть null }; ``

    Без Mapped Types вам пришлось бы вручную описывать интерфейс SignUpFormErrors, дублируя имена полей. При изменении SignUpForm вам пришлось бы менять и тип ошибок. С Mapped Types это происходит автоматически.

    Заключение

    Мы рассмотрели три мощных столпа продвинутого TypeScript:

  • Дженерики позволяют писать переиспользуемый код, сохраняя типизацию.
  • Ограничения (Constraints) сужают допустимые типы дженериков, позволяя безопасно использовать их свойства.
  • Сопоставляемые типы (Mapped Types) позволяют программно трансформировать существующие типы, создавая новые структуры на основе старых.
  • В следующей статье мы углубимся в условные типы (Conditional Types) и ключевое слово infer`, которые позволят нам писать логику "если-то" прямо внутри системы типов.

    2. Манипуляция типами: условные типы, ключевое слово infer и операторы keyof

    Манипуляция типами: условные типы, ключевое слово infer и операторы keyof

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

    Сегодня мы превратим TypeScript в настоящий язык программирования внутри системы типов. Мы научимся писать логические условия («если тип А, то тип Б, иначе тип В»), извлекать скрытые типы из сложных структур и манипулировать ключами объектов. Это те инструменты, которые позволяют библиотекам вроде React, Redux или Prisma обеспечивать невероятный уровень автодополнения и безопасности.

    Оператор keyof: Доступ к ключам

    Оператор keyof — это фундамент для работы со структурой объектов. Если говорить просто, он берет тип объекта и возвращает объединение (union) строковых литералов его ключей.

    Базовое использование

    Рассмотрим простой интерфейс:

    Теперь UserKeys — это тип, который разрешает только эти три конкретные строки. Это невероятно полезно для функций, которые ожидают имя свойства.

    keyof и typeof

    Часто у нас нет заранее определенного интерфейса, но есть объект (константа). TypeScript позволяет комбинировать операторы. Сначала мы получаем тип объекта через typeof, а затем его ключи через keyof.

    !'name' | 'email'. | Визуализация работы оператора keyof, преобразующего интерфейс в объединение строковых литералов.

    Условные типы (Conditional Types)

    Условные типы привносят логику if-else в систему типов. Синтаксис заимствован у тернарного оператора JavaScript.

    Синтаксис

    Это читается так: «Если тип T совместим с типом U (расширяет его), то результатом будет тип X, иначе — тип Y».

    Пример: Определение типа

    Давайте напишем утилиту, которая проверяет, является ли тип строкой:

    Это кажется тривиальным, но становится мощным инструментом, когда T — это дженерик, который мы не знаем заранее.

    Распределительные условные типы (Distributive Conditional Types)

    Это одна из самых важных и неочевидных особенностей. Если T является объединением (union type), то условный тип применяется к каждому члену объединения по отдельности, а результаты объединяются.

    Представьте, что мы хотим удалить null и undefined из типа:

    Если мы используем NonNullable<string | null>, TypeScript развернет это так:

  • string extends null | undefined ? never : string -> Результат: string
  • null extends null | undefined ? never : null -> Результат: never
  • Итоговое объединение: string | never
  • Поскольку never — это пустой тип, он исчезает из объединения. Итог: string.

    Именно так работает встроенная утилита Exclude<T, U>:

    Ключевое слово infer: Вывод типов внутри условий

    Иногда нам нужно не просто проверить тип, а «вытащить» из него часть информации. Например, узнать, что возвращает функция, или какой тип лежит внутри массива. Для этого используется ключевое слово infer.

    infer работает только внутри ветки extends условного типа. Это похоже на создание переменной прямо внутри условия.

    Анатомия infer

    Представьте, что infer R — это «место для захвата» (как capture group в регулярных выражениях). TypeScript смотрит на тип и пытается понять, что должно быть на месте R, чтобы условие выполнилось.

    Пример 1: Получение типа элементов массива

    Допустим, у нас есть тип string[], и мы хотим получить string.

    Как это работает:

  • Мы спрашиваем: «T похож на массив чего-то?» ((infer R)[]).
  • Если да, назови это «что-то» буквой R и верни мне R.
  • Если нет, верни исходный T.
  • Пример 2: ReturnType

    В TypeScript есть встроенная утилита ReturnType<T>, которая получает тип возвращаемого значения функции. Давайте реализуем её сами:

    Здесь infer R стоит на месте возвращаемого значения функции. TypeScript анализирует typeof getUser и понимает, что на месте R находится объект пользователя.

    !Схематичное изображение того, как infer извлекает внутренний тип из обертки.

    Практическое применение: Рекурсивная распаковка

    Давайте объединим знания. Представьте, что у вас есть тип, который может быть Promise, или Promise внутри Promise, и так далее. Мы хотим получить конечное значение.

    Здесь мы используем:

  • Условный тип для проверки, является ли T промисом.
  • infer для извлечения того, что внутри промиса (U).
  • Рекурсию, передавая U снова в Awaited, чтобы раскрыть вложенные промисы.
  • Заключение

    Мы рассмотрели три мощнейших инструмента:

  • keyof позволяет безопасно работать с именами свойств объектов.
  • Условные типы (extends ? :) позволяют писать логику ветвления на уровне типов.
  • infer позволяет «вытаскивать» и использовать внутренние типы из сложных структур.
  • Эти инструменты превращают систему типов из статического описания данных в динамическую среду вычислений. В следующей статье мы разберем Template Literal Types — возможность создавать типы, манипулируя строками, как шаблонами.

    3. Шаблонные литеральные типы и создание собственных утилитных типов

    Шаблонные литеральные типы и создание собственных утилитных типов

    Добро пожаловать в третью часть курса TypeScript Pro. В предыдущих статьях мы освоили дженерики, ограничения, сопоставляемые типы (Mapped Types), а также научились строить логические ветвления с помощью условных типов и ключевого слова infer.

    Сегодня мы объединим эти знания с одной из самых креативных возможностей TypeScript, появившейся в версии 4.1 — шаблонными литеральными типами (Template Literal Types). Эта функциональность позволяет манипулировать строками на уровне типов так же свободно, как мы делаем это со значениями в JavaScript. Мы научимся склеивать, парсить и трансформировать строковые типы, создавая мощные утилиты для типизации событий, стилей и сложных объектов.

    Основы шаблонных литеральных типов

    В JavaScript мы используем обратные кавычки для интерполяции строк:

    Пока это выглядит просто. Магия начинается, когда вместо одного строкового литерала мы передаем объединение (Union Type).

    Распределение по объединениям

    Если в шаблон передать объединение строк, TypeScript автоматически создаст декартово произведение — все возможные комбинации строк.

    Представьте, что вы разрабатываете UI-библиотеку. У вас есть цвета и градации:

    Это позволяет описывать сложные наборы строк (например, CSS-классы или имена событий) очень компактно.

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

    Встроенные утилиты для работы со строками

    Для удобства работы TypeScript предоставляет четыре встроенных дженерика для манипуляции регистром строк. Они работают на уровне компилятора:

  • Uppercase<S> — переводит все символы в верхний регистр.
  • Lowercase<S> — переводит все символы в нижний регистр.
  • Capitalize<S> — делает первую букву заглавной.
  • Uncapitalize<S> — делает первую букву строчной.
  • Пример использования для генерации геттеров:

    TypeScript видит префикс "on:", отбрасывает его, а всё, что осталось до конца строки, помещает в переменную типа Event.

    Пример: Парсинг версий

    Мы можем использовать несколько infer в одной строке, если между ними есть разделители.

    Реализация ReplaceAll (замена всех вхождений)

    Похожий принцип используется для замены подстрок.

    !Схематичное изображение работы рекурсивного типа для замены подстрок.

    Практическое применение: Типизация CSS-классов (BEM)

    Давайте создадим утилиту, которая помогает генерировать валидные имена классов по методологии BEM (Block, Element, Modifier).

    В BEM структура выглядит так: block__element--modifier.

    Разберем user_first_name:

  • Head = "user", Tail = "first_name".
  • Результат: "user" + SnakeToCamel<"First_name">.
  • Следующий шаг: Head = "First", Tail = "name".
  • Результат: "First" + SnakeToCamel<"Name">.
  • Итог: "userFirstName".
  • Теперь применим это к объекту через Mapped Types и переименование ключей (as):

    Заключение

    Шаблонные литеральные типы превращают TypeScript из инструмента проверки типов в инструмент метапрограммирования. Мы научились:

  • Генерировать сложные строковые объединения.
  • Использовать встроенные утилиты регистра (Capitalize и др.).
  • Парсить строки с помощью infer.
  • Создавать рекурсивные типы для сложной трансформации строк.
  • Эти навыки позволяют создавать библиотеки и API с невероятно точной типизацией, которая подсказывает разработчику правильные значения еще до запуска кода. В следующей части курса мы рассмотрим брендированные типы (Branded Types) и защиту от примитивной одержимости, чтобы сделать наш код еще более безопасным.

    4. Метапрограммирование: глубокое изучение декораторов и паттернов проектирования

    Метапрограммирование: глубокое изучение декораторов и паттернов проектирования

    Добро пожаловать в четвертую часть курса TypeScript Pro. В предыдущих статьях мы исследовали границы системы типов: создавали условные типы, манипулировали строковыми литералами и использовали infer для извлечения данных. Мы научились описывать форму данных с невероятной точностью.

    Сегодня мы переходим от описания данных к изменению поведения кода. Мы займемся метапрограммированием. Это подход, при котором программа имеет знания о самой себе или может манипулировать собой. В мире TypeScript главным инструментом для этого являются декораторы и Reflection API.

    Если вы когда-либо использовали Angular, NestJS или TypeORM, вы видели магию вроде @Component, @Injectable или @Entity. В этой статье мы разберем, как эта магия работает изнутри, и научимся создавать собственные архитектурные паттерны.

    Что такое декораторы?

    По своей сути, декоратор — это просто функция, которая оборачивает другую сущность (класс, метод, свойство или параметр) для изменения её поведения или добавления метаданных.

    В TypeScript существует два стандарта декораторов:

  • Experimental Decorators: «Старый» стандарт, на котором построена большая часть экосистемы (NestJS, Angular). Требует включения experimentalDecorators в tsconfig.json.
  • Stage 3 Decorators: Новый стандарт ECMAScript, добавленный в TypeScript 5.0.
  • В рамках курса Pro мы сосредоточимся на экспериментальных декораторах с использованием метаданных, так как именно они позволяют реализовать мощные паттерны вроде Внедрения Зависимостей (Dependency Injection), которые мы разберем ниже.

    Анатомия декоратора метода

    Рассмотрим простейший пример: логирование выполнения метода.

    Метаданные и Reflection API

    Самая мощная часть декораторов в TypeScript раскрывается при использовании библиотеки reflect-metadata. Она позволяет сохранять скрытые данные о типах прямо внутри классов и методов во время компиляции, чтобы читать их во время выполнения.

    Для работы необходимо:

  • Установить пакет: npm install reflect-metadata.
  • Включить в tsconfig.json: "emitDecoratorMetadata": true.
  • TypeScript автоматически добавляет три вида метаданных при использовании декораторов: * design:type — тип свойства. * design:paramtypes — типы аргументов конструктора или метода. * design:returntype — тип возвращаемого значения.

    Паттерн: Внедрение зависимостей (Dependency Injection)

    Давайте реализуем собственный мини-DI контейнер, похожий на тот, что используется в Angular или NestJS. Это классический пример использования метапрограммирования.

    Шаг 1: Декоратор Service

    Этот декоратор просто помечает класс как «инъектируемый» и гарантирует, что TypeScript сгенерирует для него метаданные.

    Шаг 2: Реализация контейнера

    Контейнер будет отвечать за создание экземпляров классов и автоматическую подстановку их зависимостей.

    Шаг 3: Использование

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

    В этом примере DIContainer посмотрел на конструктор UserService, увидел там Logger, автоматически создал экземпляр Logger и передал его внутрь. Это и есть Inversion of Control (IoC).

    Паттерн: Валидация DTO

    Еще один популярный сценарий — декларативная валидация объектов. Вместо написания if (user.email === undefined) мы хотим использовать @IsEmail.

    Для этого нам нужно:

  • Сохранить правила валидации в метаданных свойства.
  • Создать метод validate, который проверит эти правила.
  • Декораторы и порядок выполнения

    Важно понимать порядок, в котором инициализируются декораторы. В TypeScript они применяются в следующем порядке:

  • Декораторы свойств экземпляра.
  • Декораторы методов экземпляра и их параметров.
  • Декораторы статических свойств.
  • Декораторы статических методов.
  • Декораторы класса.
  • Если к одному методу применяется несколько декораторов, они вычисляются (как фабрики) сверху вниз, но вызываются (как функции) снизу вверх (композиция функций).

    Порядок вызова: Second -> First.

    Заключение

    Метапрограммирование в TypeScript открывает двери для создания выразительных и чистых архитектур. Мы разобрали:

  • Декораторы как способ перехвата и изменения поведения классов и методов.
  • Reflection API (reflect-metadata) как мост между типами времени компиляции и логикой времени выполнения.
  • Реализацию паттерна Dependency Injection, который лежит в основе современных фреймворков.
  • Декларативную валидацию данных.
  • Эти инструменты позволяют вам писать код, который не просто выполняет бизнес-логику, но и управляет собственной структурой, делая разработку крупных приложений более масштабируемой.

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

    5. Оптимизация конфигурации tsconfig и стратегии строгой типизации проектов

    Оптимизация конфигурации tsconfig и стратегии строгой типизации проектов

    Добро пожаловать в пятую часть курса TypeScript Pro. В предыдущих модулях мы занимались «алхимией» типов: создавали сложные дженерики, манипулировали строковыми литералами и даже писали свой DI-контейнер с помощью декораторов. Мы научились выражать сложнейшие бизнес-правила через систему типов.

    Однако, даже самый совершенный код может стать бесполезным, если фундамент проекта — файл tsconfig.json — настроен неверно. Конфигурация компилятора — это не просто список флагов. Это манифест качества вашего проекта. Она определяет, насколько строго TypeScript будет следить за вами, как быстро будет собираться проект и насколько безопасно вы сможете проводить рефакторинг.

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

    Фундамент: Истинное значение strict

    Большинство разработчиков начинают проект с команды tsc --init, которая создает дефолтный конфиг. Ключевая настройка там — "strict": true. Но что она делает на самом деле?

    Это «зонтичный» флаг (umbrella flag), который включает семейство проверок. Включая его, вы активируете:

    * noImplicitAny: Запрещает неявный тип any. Если TypeScript не может вывести тип, он потребует указать его явно. * strictNullChecks: null и undefined перестают быть частью любого типа. Это, пожалуй, самая важная защита от ошибок «Cannot read property of undefined». * strictFunctionTypes: Проверяет бивариантность параметров функций (более строгая проверка колбэков). * strictBindCallApply: Проверяет типы аргументов для методов bind, call и apply. * strictPropertyInitialization: Требует, чтобы свойства класса были инициализированы в конструкторе или имели дефолтное значение. * noImplicitThis: Запрещает this с неявным типом any. * useUnknownInCatchVariables: Переменная ошибки в блоке catch получает тип unknown вместо any (начиная с TS 4.4).

    Использование strict: true — это абсолютный минимум для любого нового проекта. Однако для уровня Pro этого недостаточно.

    За пределами strict: Максимальная безопасность

    Существуют флаги, которые не входят в набор strict (часто из-за того, что они могут быть слишком педантичными для новичков), но которые критически важны для энтерпрайз-разработки.

    noUncheckedIndexedAccess

    Это один из самых полезных и одновременно раздражающих флагов. По умолчанию TypeScript считает, что доступ к массиву или объекту по индексу всегда возвращает значение.

    Включив "noUncheckedIndexedAccess": true, TypeScript автоматически добавляет undefined к результату доступа по индексу.

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

    noImplicitOverride

    Этот флаг (появился в TS 4.3) заставляет разработчиков явно помечать методы, которые переопределяют методы родительского класса, ключевым словом override.

    Зачем это нужно? Представьте, что вы переименовали метод в родительском классе, но забыли обновить наследника. Без этого флага наследник просто создаст новый метод, а старая логика переопределения сломается молча. С флагом noImplicitOverride компилятор выдаст ошибку, так как метод наследника больше ничего не переопределяет.

    exactOptionalPropertyTypes

    По умолчанию, если свойство опционально (?:), TypeScript разрешает присваивать ему undefined. Но семантически «свойство отсутствует» и «свойство есть, но равно undefined» — это разные вещи (например, при использовании Object.assign или переборе ключей).

    Включение "exactOptionalPropertyTypes": true запрещает явно присваивать undefined опциональным полям, если undefined не указан в типе явно.

    !Пирамида строгости конфигурации TypeScript.

    Оптимизация производительности компиляции

    В крупных проектах время проверки типов может достигать минут. Правильный конфиг может ускорить этот процесс в разы.

    incremental

    Этот флаг заставляет TypeScript сохранять информацию о последней компиляции в файл .tsbuildinfo. При следующем запуске компилятор перепроверит только измененные файлы и те, что от них зависят. Это маст-хэв для CI/CD пайплайнов и локальной разработки.

    skipLibCheck

    Этот флаг отключает проверку типов в файлах деклараций (.d.ts), особенно в папке node_modules. Часто библиотеки имеют конфликтующие или некорректные типы, которые вы не можете исправить. Включение этого флага значительно ускоряет компиляцию, так как TypeScript доверяет декларациям библиотек «на слово» и проверяет только ваш код.

    isolatedModules

    Если вы используете внешние сборщики (Vite, esbuild, SWC, Babel), этот флаг обязателен. Эти инструменты обрабатывают каждый файл изолированно и не имеют доступа к полной информации о типах проекта.

    Флаг "isolatedModules": true предупредит вас, если вы напишете код, который корректен для TypeScript, но не может быть безопасно транспилирован без информации о типах (например, ре-экспорт только типов без export type).

    Стратегии разрешения модулей (Module Resolution)

    С выходом TypeScript 5.0 и популяризацией ESM (ECMAScript Modules) настройки модулей изменились.

    moduleResolution: "bundler"

    Если вы пишете современное приложение на React/Vue/Svelte и используете Vite, Webpack 5 или esbuild, вам следует использовать:

    Режим bundler моделирует поведение современных сборщиков, позволяя импортировать модули без расширений и корректно обрабатывать package.json exports.

    Path Mapping (Алиасы)

    Чтобы избежать ада с относительными путями ../../../../components, используйте paths:

    Важно: TypeScript только проверяет типы и разрешает пути виртуально. Он не меняет пути в скомпилированном JavaScript. Вам нужно настроить ваш сборщик (Vite, Webpack) или рантайм (через tsconfig-paths для Node.js), чтобы он тоже понимал эти алиасы.

    Стратегии миграции на строгую типизацию

    Представьте, что вы приходите на проект, где strict: false, и тысячи ошибок типа any. Включить strict: true сразу невозможно — проект просто перестанет собираться. Как быть?

    Стратегия 1: Постепенное ужесточение

    Включайте флаги по одному. Порядок сложности (от простого к сложному):

  • noImplicitThis
  • strictBindCallApply
  • strictFunctionTypes
  • strictPropertyInitialization
  • strictNullChecks (самый болезненный, но полезный)
  • noImplicitAny (обычно самый массовый)
  • Стратегия 2: Использование @ts-expect-error

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

    * // @ts-ignore: Полностью игнорирует следующую строку. Избегайте этого. * // @ts-expect-error: Говорит компилятору: «Я знаю, что здесь ошибка, подави её». Если в будущем вы исправите код и ошибки не будет, TypeScript заставит вас удалить этот комментарий. Это отличный маркер технического долга.

    Стратегия 3: Базовая линия (Baseline)

    Существуют инструменты (например, better-docs или скрипты на базе tsc), которые позволяют сохранить текущие ошибки в отдельный файл (baseline) и игнорировать их, но не допускать появления новых ошибок. Это позволяет заморозить технический долг и рефакторить его постепенно.

    Интеграция с ESLint

    tsconfig.json отвечает за типы, но не за стиль кода или потенциальные логические ошибки, не связанные с типами. Здесь вступает @typescript-eslint.

    В современном стеке TSLint мертв. Используйте только ESLint.

    Критически важные правила для Pro-разработки: * @typescript-eslint/no-explicit-any: Предупреждает об использовании any. * @typescript-eslint/no-floating-promises: Требует обрабатывать промисы (await или catch), предотвращая «потерянные» асинхронные операции. * @typescript-eslint/await-thenable: Запрещает использовать await с не-промисами.

    Для работы этих правил ESLint должен «видеть» ваш tsconfig.json:

    Заключение

    Конфигурация tsconfig.json — это баланс между безопасностью, скоростью разработки и скоростью сборки.

  • Всегда начинайте с "strict": true.
  • Для повышения надежности добавляйте "noUncheckedIndexedAccess": true.
  • Для ускорения сборок используйте "incremental": true и "skipLibCheck": true.
  • Настраивайте moduleResolution в соответствии с вашей средой выполнения (Node.js или Bundler).
  • Правильно настроенный проект не только ловит баги до их появления в продакшене, но и служит отличной документацией, показывая намерения разработчиков через строгие ограничения.

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