Comments 43
Статья огонь, спасибо. Отправил коллегам версию читать через гугл-транслэйт.

А как получить в рантайме список опций литерального перечисления, для тайпгварда, например?


Вообще тайпгварды кто пишет честные для того же обмена с сервером?

Чтобы проверить в рантайме, что с сервера действительно пришли соответствующие контракту данные, можно использовать например io-ts.

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

Увы, штатными средствами обратная задача нерешаема. Если только распарсить исходник, определить типы и на основании них сделать какую-то ручную кодогенерацию. И все это куда более трудозатратно, чем пойти от динамического typeguard'а и статически определить его тип.

В компиляторе-то она решена, вот только в рантайм это не вынесено.

Typescript Non-goals:

5. Add or rely on run-time type information in programs, or emit different code based on the results of the type system. Instead, encourage programming patterns that do not require run-time metadata.

В результате получаем одну из трёх ситуаций:


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

Еще typescript-is очень хорош, за ним будущее (но требуется пропатченный tsc, хотя его подключить не так уж и сложно).

const getArrayLength = (array: any[]) => array.length

В тайпскрипте можно писать лямбды с обобщенными аргументами типов:


const getArrayLength = <T>(array: T[]) => array.length

const getArrayLength = (array: unknown[]): number => array.length

полагаю — это идеальный вариант. Ни any, ни генерики тут не нужны.

Почему вы считаете тип unknown более предпочтительным, чем генерик? И зачем явно указывать возвращаемый тип для тела функции из одного выражения?

Почему вы считаете тип unknown более предпочтительным, чем генерик?

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


И зачем явно указывать возвращаемый тип для тела функции из одного выражения?

В данном случае это просто вкусовщина. не более.

А зачем нужен дополнительный тип-параметр, который фактически не используется нигде в функции? Мне тоже кажется, что лучше честно указать top-тип и не привлекать дополнительных сущностей.
По поводу второго вопроса – это может быть просто полезная привычка писать возвращаемый тип для самопроверки, или же привычка начинать написание функции с описания её типа

Или, как говорил мне CTO на одном проекте: "да, компилятор или IDE тип вывести могут, но я не компилятор, а читаю твой код я".

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

any хорош и удобен в распаковках типов. Посмотрим на встроенные утилиты, извлекающие аргументы или возвращаемое значение из функций:

type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;


Еще классные примеры — в статье, которую я уже приводил выше.

Можно ли тут использовать unknown?
Да (с поправкой на …args)
Нужно ли тут использовать unknown?
¯\_(ツ)_/¯

Не совет, но любопытное наблюдение: система типов TS полна по Тьюрингу. Это значит, что можно писать программы, которые будут выполняться на уровне типов во время компиляции. По ссылке есть примеры калькулятора, считающего выражения на типах, и даже небольшого языка c условиями :)


Вообще говоря, обычно дизайнеры языков стараются делать системы типов НЕ полными по Тьюрингу, потому что с полнотой приходит проблема останова: можно написать такие типы, которые заставят компилятор и среду разработки ощутимо подлагивать (см. шаблоны С++). Но тем не менее, выглядит очень впечатляюще.

Теоретически-то полна, а практически даже с самыми мало-мальски сложными рекурсивными типами tsc крэшится, и issue висит open больше года. Вообще, в TS все очень хрупко с выводом типов (если случаи не тривиальные), много работы еще предстоит (и много проделано). Много тупиков (например, arr[0] при arr: string[] имеет тип string и уже никогда не будет типа string|undefined; другой пример — это тип Record<string, Xyz>, который широко применяется в большом числе модулей, но при этом имеет мало смысла, ибо a[x] будет иметь тип Xyz для любой строки x, что, очевидно, просто не может быть на практике; все из-за того, что «опциональность» — признак ключа, а не значения). Примеры можно продолжать (хотя бы ограниченность типизации статической части класса чего стоит). Есть сомнения, что процесс вообще сходящийся, глядя в динамике на то, что происходит в issues.

Интересно еще, что tsc (вернее, его главная либа) — это, по сути, один большой js-файл (больше сотни тысяч строк), очень независимый, в котором все-все. Конечно, он автоматически бандлится из маленьких, но сам факт занятен.
например, arr[0] при arr: string[] имеет тип string и уже никогда не будет типа string|undefined

И слава богу! Это прекрасный пример сознательного дизайн-решения, когда user expreience поставили впереди формальной логики. Если так сделать, пользоваться массивами станет невыносимо.

TS ощущается как типизация на честном слове.
Есть риск увязнуть, начиная от ревью с ЧФ, заканчивая best practices, которые казались реально неплохими.
Для крупных и сложных проектов 10 раз подумал бы прежде, чем внедрять именно TS.

Сама статья хороша.
TS ощущается как типизация на честном слове.

Если вы про soundness — то да, типы в TS являются unsound, и тому есть вполне логичное объяснение. Язык проектируется таким образом, чтобы давать существующей экосистеме условные 80% пользы за 20% затрат. Да, это не 100, но это разумный компромисс. Если для вас этого недостаточно — есть много других компилируемых в JS языков c гарантией soundness (Purescript, Elm, ...) — этой проблемы у них нет, но есть другие :)


Для крупных и сложных проектов 10 раз подумал бы прежде, чем внедрять именно TS.

А что бы вы предложили для крупных и сложных проектов?

А что бы вы предложили для крупных и сложных проектов?

Как раз то, что вы предложили выше. Имел опыт с ReasonML и остался доволен результатом, но признаюсь, перестроиться было непросто.

Есть подозрение, что найти достаточное количество разработчиков на ReasonML будет непросто. И как у него дела с поддержкой всяких популярных библиотек-фреймворков? Например Vue, Bootstrap, прости-хоспади-jQuery?

Соглашусь, что ReasonML не на слуху и в русскоязычном пространстве, например, весьма мало упоминаний. Но в последнее время заметил, что комьюнити растет, выходят туториалы и гайды. Есть канал в Discord, там даже проскакивают вакансии.

Поддержка любых сторонних библиотек решается через binding и там на первый план выходит BuckleScript, тут нужна практика.

К счастью, биндить слишком часто мне не приходится, хотя это довольно занимательный процесс. Есть и готовые биндинги, но их не так много пока.
За Vue не скажу, но React вполне комфортно использовать reasonml.github.io/reason-react

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

Так почти с любой новой технологией. «Новой» не только в принципе, но и для себя в частности. Мне в этом плане повезло войти в проект, где это уже использовалось, что сильно помогло разобраться во многих вещах.

Спасибо за статью. Расскажите подробнее про typescript, dto и swagger. Откуда берутся типы, как формируется документация, что, где и чем валидируется и вот все это?

Swagger построен на спецификации OpenApi, которая в свою очередь предоставляет множество вариантов по генерации данных. Вот, например, один из них. Можно поискать и другие, тут у меня нет рекомендации.

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

Если контракты на бекенде меняются нечасто, то же самое можно делать и вручную. Главная идея в том, что отдельный слой типов и интерфейсов отвечает за контракты с бэкендом и меняется только при изменении этих контрактов.
Совет 8: литеральный тип vs enum

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

Так же, типы часто приходится экспортировать/импортировать, и если таких типов много или если где-то используется много компонентов, и каждый из них содержит в себе типизированный параметр, то количество импортов может сильно вырасти. Для себя я вывел идею того, что типы, которые будут использоваться вне компонента, но вместе с ним, можно записать в статичные поля и получится что-то подобное:
enum FlexDirection {...};
enum AlignItems {...};

interface StaticFields {
  FlexDirection: FlexDirection;
  AlignItems: AlignItems;
}

const FlexContainer: React.FC<Props> & StaticFields = () => {...};

FlexContainer.FlexDirection = FlexDirection;
FlexContainer.AlignItems = AlignItems;

<FlexContainer flexDirection={FlexContainer.FlexDirection...}>...</FlexContainer>

Учет статистики вели грубо, просто по количеству файлов (несправедливый подход, но для визуализации достаточно). Сама диаграмма по данным легко строится в программах типа excel (мы же использовали встроенный макрос из Atlassian Wiki).
Использование TypeScript в проекте изменяет подход к написанию кода. Становится легче изучать код, ...

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


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


Говорят что с ростом кодовой базы тайпскрипт начинает помогать разработчику… Но я за год разработки похоже так и не достиг этого количества кодовой базы, и мои коллеги того же мнения).

Да, но при хорошем знании тайпскрипта код действительно легче и изучать, и рефакторить. Проптайпы есть только для реактовых компонент, всё остальное приходится читать по-старинке, держа в уме возможные значения всех переменных. Когда я перешёл на тайпскрипт, внезапно оказалось, что в фокусе внимания надо держать гораздо меньше вещей, потому что всю эту информацию за тебя обрабатывает компилятор. Можно не проверять каждый раз, что в переменной лежит правильное значение – если это не так, то машина подскажет об этом сама.


А время, которое уходит на написание типов, я считаю, окупается с лихвой – хотя бы потому, что написание типов – это то же проектирование частей приложения, только результаты проектирования мы записываем формальным и частично проверяемым автоматическими средствами способом.

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

Очень слабая альтернатива. Особенно для вложенных типов (если вы конечно не делаете глубоких shape-ов). К тому же весьма многословная и очень слабая в инструментарии. Такое удовольствие доставляет потом вычищать все эти prop-types заменяя их на полноценные типы.


TypeScript определённо замедляет процесс разработки нового кода. Особенно если у вас много generic-кода. Там бывает такие ребусы встают, что можно рабочий день тупо на 1 тип потратить, с непривычки.


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


TypeScript имеет очень высокий уровень входа по сравнению с JavaScript. Потому что ему надо всё доказывать. И не срезать углы. А несрезание углов очень нетривиальная задача. В итоге, мне кажется, все кто в итоге оказался им сильно недоволен, по сути просто так и не овладели этими техниками — затипить то что нетипизируется. Там бывает всё очень непросто. И я очень даже понимаю тех, кто не осилил — иногда это как пробивать головой стену. Ошибки чаще всего нечитаемые. Инструментария как их разрулить вроде нет. Уровней вложенности матрёшек может быть 10+. Всякие "and 21 overloads". Но если осилить этот путь, то потом уже к JavaScript назад дороги нет )

Ещё добавлю — если рефакторинг кода + починка\поиск багов не являются очень большим %-ом от всего времени разработки вашего приложения (т.е. вы пишите write-only код), то, пожалуй, TypeScript вам не нужен.

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

Вот пара идей, как с этим можно справляться:

В команде/компании есть адвокат ts — хорошо разбирается в теме и приходит на помощь. Также есть отдельный канал по ts, в который можно написать с вопросом.

Оцениваем, насколько стоит вкладываться в типизацию конкретного блока.
Например, есть блок легаси кода на js, который написали год назад и с тех пор не меняли. Фича готова, протестирована, заехала в продакшен. Если она и будет меняться, то вероятно с каким-то глобальным рефакторингом. Врядли стоит тратить много сил на разборки с типами, покрыть основные входы-выходы может быть вполне достаточно.
В команде/компании есть адвокат ts — хорошо разбирается в теме и приходит на помощь

Для большинства компаний это очень утопичное допущение. Найти просто хорошего senior фронтенд разработчика — очень сложная задача. А найти ещё и эксперта в области TypeScript — прямо большая удача.


Например, есть блок легаси кода на js, который написали год назад и с тех пор не меняли

Хорошо работает подход — типизировать по верхам при помощи ts-комментариев.

Для большинства компаний это очень утопичное допущение. Найти просто хорошего senior фронтенд разработчика — очень сложная задача. А найти ещё и эксперта в области TypeScript — прямо большая удача.


Я не предлагал искать отдельного эксперта по TS. Под «адвокатом ts» имею ввиду неформальную роль — человека, которому тема интересна и который готов повышать свой уровень, делиться знаниями с окружающими.

Хорошо работает подход — типизировать по верхам при помощи ts-комментариев.

Точно!

Таких адвокатов надо контролировать: они могут чуть ли не неделями создавать правильные семнадцатиэтажные типы вместого того, что бы сделать as или any. А когда создадут тратить часы на разбор того, почему рабочий js код не компилируется.

Only those users with full accounts are able to leave comments. Log in, please.