Как использовать библиотеку 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!