В настоящее время разработка любого современного фронтэнд-приложения сложнее уровня hello world, над которым работает команда (состав которой периодически меняется), выдвигает высокие требования к качеству кодовой базы. Чтобы поддерживать уровень качества кода на должном уровне, мы во фронтэнд-команде #gostgroup идём в ногу со временем и не боимся применять современные технологии, которые показывают свою практическую пользу в проектах компаний самого разного масштаба. О статической типизации и её пользе на примере TypeScript было много сказано в различных статьях и поэтому сегодня мы сосредоточимся на более прикладных задачах, с которыми сталкиваются фронтэнд-разработчики на примере любимого нашей командой стека (React + Redux). При написания кода на TypeScript (здесь и далее в тексте будет подразумеваться стек сабжа) многие жалуются на то, что приходится тратить много времени на написание типов вручную. Хороший пример, иллюстрирующий проблему, функция-коннектор connect из библиотеки react-redux. Давайте взглянем на код ниже: В чём здесь проблема? Проблема в том, что для каждого нового свойства, инжектируемого через коннектор, мы должны описать тип этого свойства в общем типе свойств компонента (React). Не очень интересное занятие, скажите вы, всё-таки хочется иметь возможность собирать тип свойств из коннектора в один тип, который потом один раз "подключать" к общему типу свойств компонента. У меня хорошая новость для вас. Уже сегодня TypeScript позволяет это сделать! Готовы? Поехали! TypeScript не стоит на месте и постоянно развивается (за что я его люблю). Начиная с версии 2.8 в нём появилась очень интересная функция (conditional types), которая позволяет производить маппинги типов на основе условных выражений. Не буду вдаваться в подробности здесь, а просто оставлю ссылку на документацию и вставлю кусок кода из неё в качестве иллюстрации: Как эта функция помогает в нашем случае. Посмотрев в описание типов библиотеки react-redux, можно найти тип InferableComponentEnhancerWithProps, который отвечает за то, чтобы типы инжектированных свойств не попали во внешний тип свойств компонента, которые мы должны явно задавать при инстанцировании компонента. У типа InferableComponentEnhancerWithProps есть два обобщенных параметра: TInjectedProps и TNeedsProps. Нас интересует первый. Давайте попробуем "вытащить" этот тип из настоящего коннектора! И непосредственно вытаскивание типа на реальном примере из репозитория(который можно склонировать и запустить там тестовую программу): В примере выше мы делим подключение к хранилищу (Redux) на два этапа. На первом этапе мы получаем компонент высшего порядка storeEnhancer (он же тип InferableComponentEnhancerWithProps) для извлечения из него инжектируемых типов свойств с помощью нашего типа-помощника TypeOfConnectи дальнейшего объединения (через интерсекцию типов &) полученных типов свойств с собственными типами свойств компонента. На втором этапе мы просто декорируем наш исходный компонент. Теперь, что бы вы не добавили в коннектор, это автоматически будет попадать в типы свойств компонента. Здорово, то, чего мы и хотели добиться! Внимательный читатель заметил, что генераторы экшенов (для краткости далее по тексту упростим до термина экшена) с сайд-эффектами (thunk action creators) проходят дополнительную обработку с помощью функции unboxThunk. Чем же вызвана такая дополнительная мера? Давайте разбираться. Сначала посмотрим на сигнатуру такого экшена на примере программы из репозитория: Как видно из сигнатуры, наш экшен не сразу возвращает целевую функцию, а сначала промежуточную, которую подхватывает redux-middleware для возможности произведения сайд-эффектов в нашей основной функции. Но при использовании этой функции в подключенном виде в свойствах компонента, сигнатура этой функции сокращается, исключая промежуточную функцию. Как это описать в типах? Нужна специальная функция-преобразователь. И снова TypeScript показывает свою мощь. Сначала опишем тип, который убирает промежуточную функцию из сигнатуры: Тут, помимо условных типов, используется совсем свежее нововведение из TypeScript 3.0, которое позволяет выводить тип произвольного (rest parameters) количества аргументов функции. Подробности смотрите в документации. Теперь остается вырезать из нашего экшена лишнюю часть довольно жёстким образом: Пропустив экшен через такой преобразователь, мы на выходе имеем нужную нам сигнатуру. Теперь экшен готов для его использования в коннекторе. Вот так, путём нехитрых манипуляций, мы сокращаем наш ручной труд при написании типизированного кода на нашем стеке. Если пойти немного дальше, то можно также упростить типизирование экшенов и редьюсеров, как мы это сделали в redux-modus. P.S. При использовании динамической привязки экшенов в коннекторе через функцию и redux.bindActionCreators нужно будем самому позаботится о более правильной типизации этой утилиты (возможно через написание своей обёртки). Update 0
Если кому-то показалось это решение удобным, то вот тут можно поставить лайк, чтобы тип-утилиту добавили в пакет @types/react-redux. Update 1
Ещё немного типов, с помощью которых не нужно явно указывать тип инжектируемых пропсов хока. Просто берём хоки и вытаскиваем из них типы: Update2
Тип из сабжа теперь есть в @types/react-redux (ConnectedProps). Интересное про фронтэнд https://t.me/frontendart