Как использовать библиотеку Redux и интегрировать ее в приложение SwiftUI

Redux действительно крут, SwiftUI еще круче, а наличие обоих в одном приложении - рай на земле. Итак, еще раз я попытаюсь убедить вас, что это лучшая (или худшая) архитектура приложения SwiftUI.



За последние несколько недель я развил свою небольшую наивную библиотеку Redux SwiftUI. Я добавил файл readme, который почти не объясняет, как заставить его работать в вашем приложении, но у меня все равно есть рабочий пример.

Что еще более важно, я добавил StoreProvider, StoreConnector и ConnectedView.

Я не могу достаточно отблагодарить Алексея Демедецкого , так как новые дополнения во многом вдохновлены его более ранней работой над SwiftUI и Redux. Я добавил его в свою библиотеку и с его помощью отредактировал часть MovieSwiftUI. Он творит чудеса с моей ранее существовавшей архитектурой Redux.

Я не буду говорить о концепциях Redux в этой статье, это руководство о том, как использовать библиотеку и интегрировать ее в ваше приложение SwiftUI.

Это, конечно, даст вам общее представление о том, как все это работает вместе, но это больше касается архитектуры компонентов. За пояснениями вы можете обратиться к моей предыдущей статье, указанной в первом абзаце.

Тем не менее, давайте углубимся в код.

Любой проект SwiftUI будет иметь SceneDelegate - это новый класс с iOS 13 / macOS 10.15, который управляет экземплярами Windows и представлением вашего приложения.

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

В нашем случае мы также создадим начальный state Redux и предоставим его нашему store, а затем обернем его с помощью StoreProvider вокруг нашего корневого представления.

За кулисами StoreProvider - это просто причудливое имя для представления SwiftUI, которое установит наш store как environmentObject нашего представления содержимого.

Это можно было бы сделать с помощью HomeView().environmentObject(store), но наличие этой оболочки означает, что для меня безопасно развивать базовый API, не касаясь кода.

С SwiftUI вам нужно вставить свой environmentObject в иерархию представлений только один раз, если это сделано в корне, как здесь. На данный момент все еще существует ошибка с .sheet, где она не прошла корректно, но она должна быть устранена в следующих нескольких бета-версиях.

Создание реактивного компонента

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

Когда вы будете dispatch action (для получения данных или из-за того, что пользователь что-то запустил в вашем приложении, и ему нужно обновить свое состояние), ваш state изменится. И поскольку свойства обновляются в магазине, ваш компонент должен обновляться в соответствии с тем, что ему нужно.

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

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

Это контекстное меню, которое вы получите, когда нажмете с силой на любого актера / режиссера / т. Д. в MovieSwiftUI, чтобы вы могли добавить этого человека в свой фан-клуб.

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

Первое, что нам нужно сделать, это привести его в соответствие с ConnectedView.

ConnectedView - это протокол, предоставляемый SwiftUIFlux, и, реализовав новую функцию body, он объединит ваше представление вокруг представления, которое реагирует на ваше AppState изменение, поскольку оно введено как @EnvironmentObject.

Второе, что нам нужно определить, - это Props. Это очень просто; нам нужен только один Bool, чтобы знать, состоит ли этот человек в нашем фан-клубе или нет.

Затем у нас есть константа people ID, которая передается при внешнем конструировании представления, например:

Теперь самое интересное: map(state:dispatch:) - это функция, которую нужно реализовать, если вы хотите соответствовать ConnectedView.

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

Он будет вызываться непосредственно перед вызовом новой body(props:) функции, то есть непосредственно перед фактическим отображением или отображением представления.

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

Это работает, потому что оболочка свойств Binding предоставляет инициализатор, в который вы можете передавать функции в качестве параметров для getValue и setValue, и это то, что мы здесь делаем.

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

Но для пользовательской части компонента все, что он увидит и к чему будет иметь доступ, - это красивое props.isInFanClub логическое значение.

Затем вызывается функция body(props:), которая позволяет вернуть пользовательский интерфейс компонента со свойствами переданного представления. И, как вы можете видеть в нашем случае, это просто Button с HStack, которые предоставляют Image и Text.

Когда мы касаемся Button, мы запускаем наш action, переключая значение boolean нашего props.isInFanClub.

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

Его можно полностью протестировать в обоих состояниях путем внедрения внешних свойств, и его легко предварительно просмотреть на холсте Xcode.

Это был простой компонент. Ниже приведен код более сложного; это тоже ContextMenu, но для фильмов.

Я предоставил полный код, потому что он раскрывает еще несколько вещей, которые вы можете делать с помощью реквизита.

Здесь я передаю функции для выполнения своих действий в функцию map(state:dispatch:). Я мог бы сделать это и с Binding<Bool>, но в этом примере я сделал это по-другому.

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

Я надеюсь, что эта статья прольет свет на SwiftUI + Redux!