В этой статье я разберу очень простое одностраничное приложение React + Redux, написанное на Typescript с использованием параметра strict компилятора. Имея эту отправную точку и немного дисциплины в программировании, мы можем написать программу, которая:

1. Не имеет переменной типа any.

2. Не имеет возможности ошибки во время выполнения.

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

Если вы хотите перейти к изюминке, файл index.tsx содержит всю программу метеостанции, и следующее короткое видео демонстрирует ее работу:

Теперь разберем программу:

Импорт модулей

Я считаю полезным, особенно с функциями IDE, включенными Typescript, импортировать все из сторонних библиотек под заданным именем. В случае с парой простых вспомогательных функций, которые я написал — getNumberOr и getStringOr — я думаю, что импорт этих функций по имени — это нормально.

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

Класс WeatherState, названный так потому, что литерал объекта, возвращаемый конструктором класса, будет помещен в состояние Redux под ключом «погода» — имеет несколько интересных свойств:

1. Он имеет только метод-конструктор, поэтому экземпляры WeatherState представляют собой простые объекты, которые можно распространять на другие простые объекты, свободно передавать и использовать в Redux и т. д.

2. Его конструктор принимает только «Partial‹Constructable›», что позволяет нам обрабатывать все случаи аргументов конструктора с помощью простого if/else. Если аргумент «json» является объектом — а он обязательно будет, если он присутствует — тогда мы обрабатываем данные о ветре и температуре из JSON безопасным для типов способом. В противном случае единственным другим случаем является передача некоторой части WeatherState, поэтому в другом случае мы присваиваем эти значения переменным экземпляра this.

3. Независимо от того, используется ли if или else, мы всегда устанавливаем описание ветра последним, так как оно зависит от скорости ветра. Это завершает построение WeatherState.

Действия Redux с безопасным типом

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

Безопасность типов здесь достигается за счет аннотирования типов аргументов для error() и fetched() в сочетании с аннотацией типа возвращаемого значения, где WeatherAction может иметь только допустимые типы «ошибка» и «json», если они присутствуют.

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

Редукторы Redux с безопасным типом

Несколько интересных наблюдений здесь:

1. Функция Weather () получает значения по умолчанию для «состояния» и «действия», поэтому, даже если функция редуктора вызывается без аргументов, она будет создавать правильный тип для объекта «погода» в состоянии Redux.

2. Функция Weather() может возвращать только WeatherState, что дает надежную гарантию того, что вся структура данных будет доступна для использования в компонентах, и что каждое содержащееся в ней значение всегда будет иметь ожидаемый тип. Попытка сослаться на что-то вроде this.props.weather.wind.direction будет работать всегда и гарантированно будет содержать одно из значений перечисления Model.Direction.

3. Мне не нужно выполнять проверки во время выполнения, чтобы убедиться, что в случае с Action.Types.FETCHED этот action.json на самом деле является объектом. Это уже защищено аннотированием функции создания действия, поэтому здесь я могу просто назначить ее.

Type-safe Redux-Observable Epics

Используя Redux-Observable, асинхронность управляется с помощью так называемых эпиков. Эпик — это функция, которая подключается к Redux с помощью промежуточного программного обеспечения, получает в качестве аргумента поток и создает на выходе одно или несколько действий Redux.

(В реальной программе эпик не будет использовать Math.random() для генерации своего JSON: JSON будет поступать из сообщения HTTP GET или веб-сокета.)

Для этого эпика, чтобы имитировать сбои в реальном мире и общую ненадежность данных, я рандомизирую по двум измерениям: во-первых, эпик имеет 20%-й шанс вернуть наше действие ОШИБКА — это имитирует недоступность конечной точки, возврат 500 ответов или другое. недостойное поведение такого рода. Во-вторых, если мы не выйдем из строя сразу, то поля, которые могут присутствовать в возвращенном JSON, будут случайным образом присутствовать или нет, чтобы еще больше имитировать ненадежность конечной точки — на этот раз в случае, когда он не полностью, а где только некоторые поля отсутствуют и/или имеют значения ожидаемого типа.

Типобезопасные компоненты React

Один из наиболее распространенных источников ошибок в самых больших программах React, над которыми я работал, — это попытка сослаться на свойство переменной, которая во время выполнения считается объектом, но на самом деле не определена (или, возможно, имеет значение, но неправильного типа). Существует ряд инструментов и подходов, помогающих смягчить эту проблему, в том числе механизм React PropTypes и написание большого количества условного/защищенного кода внутри методов компонента.

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

Машинопись в помощь! Предоставляя интерфейс Props для компонента WeatherStationCore, я могу быть уверен, что всякий раз, когда я ссылаюсь на «this.props.weather», это на самом деле WeatherState со всеми предоставляемыми гарантиями.

Еще одна деталь — это аннотирование типа функции mapStateToProps, который в этом примере называется просто «карта», чтобы мы могли убедиться, что правильные интерфейсы реквизитов состояния Redux и реквизитов конструктора компонентов известны методу «connect» ReactRedux.

Подключитесь и запустите приложение

Во-первых, мы создаем хранилище Redux, которое представляет собой объект с ключом «погода», который связан с функцией Reducer.weather.

Затем мы применяем наше промежуточное ПО Epic, в котором зарегистрирован Epic.fetchWeather.

Наконец, мы создаем экземпляр Component.WeatherStation без аргументов конструктора (он получает и диспетчеризацию, и погоду из состояния Redux) и монтируем WeatherStation в DOM.

Ключевые соображения и выводы

В этой программе нет скалярной переменной, которая имеет тип «любой», и нет части значения комплексной переменной, которая имеет любой тип «любой». Каждое значение учитывается и гарантированно относится к своему типу.

Стоит отметить, что Typescript не привел меня к цели сам по себе. По пути мне пришлось соблюдать некоторую дисциплину. Например, несмотря на то, что здесь используется строгая настройка компилятора, я решил использовать в своей модели getStringOr и getNumberOr, чтобы гарантировать, что правильные типы назначаются из того, что в конечном итоге извлекается из полученного JSON. Я мог бы так же легко назначать значения из getOrElse, который возвращает тип любой. Компилятор это позволит!

Наконец, хотя эта программа является небольшим и простым примером, в Athlinks мы добились успеха с программой, которая почти в 200 раз больше этого примера: дизайн этой программы очень хорошо масштабируется, и я считаю, что это очень помогает в снижении сложности программы. что, в свою очередь, делает ошибки менее вероятными (и их легче найти и исправить, когда они все-таки возникают).

Предыдущая статья об использовании React Native и Typescript при разработке мобильного приложения Athlinks: React Native, Javascript & Typescript