Я использовал TypeScript в нескольких проектах и ​​хотел вкратце изложить некоторые мысли о дженериках. Чтобы быть ясным, универсальные шаблоны не являются новой конструкцией языка программирования и существовали в таких языках, как C # и Java, за десятилетия до TypeScript. Тем не менее, я нахожу обобщенные шаблоны интересными в контексте TypeScript, потому что я их очень часто вижу и использую.

Во-первых, что такое дженерики? Я думаю о дженериках как о способе представления типов без явного определения типа. Универсальный тип является универсальным в том смысле, что он похож на параметр функции - он может представлять что угодно. Также, как параметр в функции, его значение может быть передано как аргумент (через <SomeType>) и на него можно ссылаться в теле функции. Это качество - одна из многих причин, по которым дженерики более эффективны, чем просто использование типа any. Официальные документы TypeScript по дженерикам - отличный ресурс о том, когда использовать дженерики, и я их настоятельно рекомендую.

Ниже приведены некоторые варианты использования дженериков, которые мне показались полезными.

Обещания

В TypeScript промисы могут быть инициализированы таким образом, что они «блокируют» общий тип в типе:

Источник обещания делает возможным вышеперечисленное предупреждение. IDE используют источник, чтобы определить, что обратный вызов в конструкторе обещания должен возвращать что-то типа T, PromiseLike<T> или undefined, где T в данном случае - это число. Обратите внимание, что PromiseLike здесь - отдельный вид.

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

Теперь проверьте эквивалент TypeScript:

Только во втором примере разработчик может с некоторой уверенностью узнать, что обещание преобразуется в UserProfile (ну, или undefined, или PromiseLike<UserProfile>). Без TypeScript необходимо смотреть на определение функции и возвращаемое значение. Даже в этом случае имя lookupProfile может быть неточным в зависимости от того, как оно было реализовано; TypeScript по крайней мере не сможет скомпилировать, если возвращаемый тип не является UserProfile.

Важно отметить, что изменения кода в lookupProfile прекрасно обрабатываются универсальными типами TypeScript. Допустим, функция может возвращать профиль администратора или профиль пользователя - вы можете просто изменить тип на Promise<UserProfile | AdminProfile>.

Компоненты React

React.SFC - функциональный компонент React без сохранения состояния - имеет определение типа, подобное приведенному ниже:

В приведенном выше примере параметр типа также передается от propTypes и defaultProps до ValidationMap и Partial, соответственно, которые также принимают общие аргументы.

Написание компонентов без TypeScript может выглядеть так:

А теперь с TypeScript:

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

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

Type '{ message1: number; }' is not assignable to type 'IntrinsicAttributes & SomeProps & { children?: ReactNode; }'.
  Property 'message1' does not exist on type 'IntrinsicAttributes & SomeProps & { children?: ReactNode; }'.

Опять же, эта ошибка не возникает в случае, отличном от TypeScript. Компонент React все еще обрабатывает, а в div просто нет текста.

Аполлон

Клиент Apollo часто использует дженерики на протяжении всего результата. В этом обсуждении я выделяю только ApolloQueryResult и HOC graphql, но будьте уверены, универсальные шаблоны широко используются в кодовой базе Apollo.

ApolloQueryResult (источник здесь, возвращенный из запроса или мутации) принимает общий тип, который описывает data в ответе graphql. Общий аргумент передается свойству data в результате Apollo. Преимущества этого типа аналогичны преимуществам обещаний TypeScript (он фактически действует как общий аргумент, который принимает обещание).

Я использовал Apollo без TypeScript и помню, как меня расстраивало то, что разные компоненты извлекали разные части ApolloQueryResult - некоторые компоненты использовали loading, networkStatus и / или errors. Свойство data было особенно трудным для работы, потому что знание его формы - по крайней мере для меня - обычно требовало регистрации ответа.

TypeScript делает это ведение журнала ненужным (ну, менее необходимым - чтобы было ясно, что невозможно знать, что сервер отправит обратно во время компиляции). Apollo предоставляет здесь песочницу кода с отличным примером, часть которого скопирована ниже. Обратите внимание, что в этом примере фактически не используется ApolloQueryResult, а вместо этого используется аналогичное свойство из react-apollo, которое добавляется из ChildProps.

graphql Компонент более высокого порядка принимает два общих аргумента, один из которых описывает data из ApolloQueryResult, а другой описывает свойства, передаваемые компоненту, который оборачивается функцией более высокого порядка. graphql HOC использует преимущества, описанные в ApolloQueryResult и Компонент React и объединяет их. Теперь очевидно, что сервер graphql должен возвращать, а также то, что ожидает завернутый компонент.

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

Заключение

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

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

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

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