Как заставить TS работать на вас

от автора

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

Первая встреча с TypeScript

Чтобы понять, откуда берётся большинство проблем, начнём с самого начала. Почти все программисты переходят на TypeScript после долгой работы с JavaScript, сформировав определённые привычки и устоявшиеся практики.

Главная проблема здесь, на мой взгляд, в том, что многие пытаются просто наложить TypeScript поверх JavaScript-кода стараясь удовлетворить требованиям назойливого компилятора, вместо того чтобы писать на TS изначально.
Дальше мы разберёмся, как избавиться от старых привычек и научиться писать код так, чтобы TypeScript действительно облегчал работу.

Метод черного ящика

Одна из распространенных ошибок разработчиков, переходящих с JavaScript на TypeScript, — игнорирование типизации на этапе написания кода. Вместо того чтобы заранее описывать ожидаемые данные, многие полагаются исключительно на автоматическое выведение типов (type inference), что может приводить к неожиданным ошибкам.

Последовательно рассмотрим два подхода к разработке:

1.     Классический — пишем исполняемый код, не задумываясь о типах;

2.     TypeScript-ориентированный — определяем требования к типам, затем реализуем логику.

Проблема автоматического вывода типов

Допустим, мы разрабатываем компонент, который должен корректно обрабатывать ширину элемента так же, как это сделано, например, в компоненте VCard из Vuetify:

<VCard width="100" /> <VCard width="100px" /> <VCard width="100%" />

Начнем с привычного подхода — просто напишем код без явного указания типов:

function defineElementWidth(value: string) { // Тип не проверяется 😱 return { width: `${value}рх`, // ❌ На самом деле px - кириллица 🤬 }; }  // ❌ Можно передать заведомо невалидное значение defineElementWidth('Не число');

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

Явное определение типов

Теперь попробуем задать строгие ограничения:

// Явно указываем тип аргумента и возвращаемого значения function defineElementWidth(value: number): { width: `${number}px` } { // Типы проверяются на соответствие ожидаемым return { width: `${value}px`, // ✅ }; } // Ошибки обнаруживаются на этапе написания кода defineElementWidth('Не число'); // ❌ defineElementWidth(100); // ✅

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

Доработка с учетом всех вариантов

Добавим поддержку значений в px и в %, как во Vuetify:

// TypeScript сразу покажет ошибку в реализации function defineElementWidth(value: number | `${number}%` | `${number}px` ): { width: `${number}${'px' | '%'}` } { return { width: `${value}px`, // ❌ Ошибка! value уже может содержать px или % } }

TypeScript мгновенно подскажет, что возвращаемый тип не соответствует ожидаемому без запуска кода и долгой отладки.

Финальная реализация
Теперь создадим универсальное решение, которое корректно обрабатывает все допустимые значения:

type Unit = `px` | '%'; type Width = number | `${number}` | `${number}${Unit}`; type Style = { width: `${number}${Unit}` };  function defineElementWidth(value: Width): Style { if (typeof value === 'string') { const match = value.match(/^(\d+)([a-zA-Z%]*)$/)?.slice(1).filter(Boolean) ?? []; const [width = '0', unit = 'px'] = match;  return { width: `${parseFloat(width)}${unit as Unit}`, }; }  return { width: `${value}px`, }; }

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

Теперь функция корректно обрабатывает числа, строки и значения с единицами измерения, возвращает предсказуемый и строго типизированный объект, не допускает ошибок благодаря встроенной проверке типов.

Вывод

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

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

Типы и модульное тестирование

Если вы знакомы с методологией Test Driven Development (TDD), то, возможно, заметили сходство, TDD предлагает похожий подход — сначала написать тесты и только потом исполняемый код.
Этим подходы отлично дополняют друг друга, мы можем сначала определить типы и написать тесты, не имея готовой реализации..
Однако TypeScript потребует, чтобы функция была реализована, поэтому можно использовать заглушку:

// Заглушка позволяет не выдавать ошибки пока нет реализации function foo(): 'Foo' {   throw new Error('foo() is not implemented'); }  // Тест проверяет ожидаемый тип результата describe('foo()', () => {   it('foo() возвращает "Foo"', () => {     const expected: ReturnType<typeof foo> = 'Foo';     expect(foo()).toBe(expected);   }); });

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

Прокачка TS

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

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

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

Исправления встроенных типов

Некоторые встроенные типы в TypeScript небезопасны или неудобным — так сложилось исторически. К счастью, есть библиотеки, которые исправляют эти недочеты:

  1. ts-reset

  2. types-spring

Пересказывать их документацию смысла нет — она и так очень маленькая, лучше ознакомиться с ней самостоятельно по ссылкам выше.

Дополнительные утилиты

Несмотря на то, что из коробки TS имеет широкий набор утилит, со временем его оказывается недостаточно и разработчики начинают комбинировать утилиты в сложные конструкции и далеко не всегда они надежны.

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

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

Популярные библиотеки

  1. ts-toolbelt

  2. type-fest

  3. utility-types

  4. sniptt/guards

Если хочется сравнить их подробнее, рекомендую статью на Хабре, которая отлично раскрывает эту тему.

Но, чтобы показать, насколько могут быть полезны продвинутые утилиты, разберем пример на основе ts-toolbelt — библиотеки, которую мы используем в нашей команде.

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

// Из такого type Person = { name: string; age: number; salary: number; }  // Сделать такой type Person = { name: string; age?: number; salary?: number; }

Решим эту задачу, используя только стандартные утилиты:

type PersonOptional = Omit<Person, 'age' | 'salary'> & Partial<Pick<Person, 'age' | 'salary'>>;

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

Теперь попробуем решить ту же задачу с помощью продвинутых утилит:

import { Object } from ‘ts-toolbelt’;  type PersonOptional = Object.Optional<Person, 'age' | 'salary'>;

Код стал значительно короче, а главное — понятнее.
Если даже в таком простом случае разница ощутима, то при работе с по- настоящему сложными типами выгода становится еще более заметной.

Выводы

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

Программируйте на TS, а не боритесь с ним.


ссылка на оригинал статьи https://habr.com/ru/articles/899546/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *