SwiftUI bindings to connect UI to PureduxStore
PureduxSwiftUI is available through Swift Package Manager. To install it, in Xcode 11.0 or later select File > Swift Packages > Add Package Dependency... and add Puredux repositoies URLs for the modules requried:
https://github.com/KazaiMazai/PureduxSwiftUI
import PureduxSwiftUI
typealias Command = () -> Void
struct FancyView: View {
let title: String
let didAppear: Command
var body: some View {
Text(title)
.onAppear { didAppear() }
}
}
extension FancyView {
init(state: AppState, dispatch: @escaping Dispatch<Action>) {
self.init(
title: state.title,
didAppear: { dispatch(FancyViewDidAppearAction()) }
)
}
}
let appState = AppState()
let storeFactory = StoreFactory<AppState, Action>(
initialState: state,
reducer: { state, action in state.reduce(action) }
)
let envStoreFactory = EnvStoreFactory(storeFactory: storeFactory)
UIHostingController(
rootView: ViewWithStoreFactory(envStoreFactory) {
ViewWithStore { state, dispatch in
FancyView(
state: state,
dispatch: dispatch
)
}
}
)
Old API will be deprecated in the next major update. Good time to migrate to new API, especially if you plan to use new features like child stores.
RootStore
to StoreFactory
like mentioned in PureduxStore docsBefore:
let appState = AppState()
let rootStore = RootStore<AppState, Action>(initialState: appState, reducer: reducer)
let rootEnvStore = RootEnvStore(rootStore: rootStore)
let fancyFeatureStore = rootEnvStore.store().proxy { $0.yourFancyFeatureSubstate }
let presenter = FancyViewPresenter()
Now:
let appState = AppState()
let storeFactory = StoreFactory<AppState, Action>(initialState: state, reducer: reducer)
let envStoreFactory = EnvStoreFactory(storeFactory: storeFactory)
let fancyFeatureStore = envStoreFactory.scopeStore { $0.yourFancyFeatureSubstate }
let presenter = FancyViewPresenter()
StoreProvidingView
to ViewWithStoreFactory
in case your implementation relied on injected RootEnvStore
Before:
UIHostingController(
rootView: StoreProvidingView(rootStore: rootEnvStore) {
//content view
}
)
Now:
UIHostingController(
rootView: ViewWithStoreFactory(envStoreFactory) {
//content view
}
)
View.with(...)
extension to ViewWithStore(...)
in case your implementation relied on explicit storeBefore:
FancyView.with(
store: fancyFeatureStore,
removeStateDuplicates: .equal {
$0.title
},
props: presenter.makeProps,
queue: .main,
content: { FancyView(props: $0) }
)
Now:
ViewWithStore(props: presenter.makeProps) {
FancyView(props: $0)
}
.usePresentationQueue(.main)
.removeStateDuplicates(.equal { $0.title })
.store(fancyFeatureStore)
View.withEnvStore(...)
extension to ViewWithStore(...)
in case your implementation relied on injected RootEnvStore
Before:
FancyView.withEnvStore(
removeStateDuplicates: .equal {
$0.title
},
props: presenter.makeProps,
queue: .main,
content: { FancyView(props: $0) }
)
Now:
ViewWithStore(props: presenter.makeProps) {
FancyView(props: $0)
}
.usePresentationQueue(.main)
.removeStateDuplicates(.equal { $0.title })
It's minilistic UDF architecture store implementation. More details can be found here
PureduxSwiftUI allows to connect view to the following kinds of stores:
let appState = AppState()
let storeFactory = StoreFactory<AppState, Action>(initialState: state, reducer: reducer)
let envStoreFactory = EnvStoreFactory(storeFactory: storeFactory)
let featureStore = envStoreFactory.scopeStore { $0.yourFancyFeatureSubstate }
UIHostingController(
rootView: ViewWithStore { state, dispatch in
FancyView(
state: state,
dispatch: dispatch
)
}
.store(featureStore)
)
let appState = AppState()
let storeFactory = StoreFactory<AppState, Action>(initialState: state, reducer: reducer)
let envStoreFactory = EnvStoreFactory(storeFactory: storeFactory)
UIHostingController(
rootView: ViewWithStoreFactory(envStoreFactory) {
ViewWithStore { appState, dispatch in
FancyView(
state: state,
dispatch: dispatch
)
}
}
)
let appState = AppState()
let storeFactory = StoreFactory<AppState, Action>(initialState: state, reducer: reducer)
let envStoreFactory = EnvStoreFactory(storeFactory: storeFactory)
UIHostingController(
rootView: ViewWithStoreFactory(envStoreFactory) {
ViewWithStore { featureSubstate, dispatch in
FancyView(
state: substate,
dispatch: dispatch
)
}
.scopeStore({ $0.yourFancyFeatureSubstate })
}
)
Child store is special. More details in PureduxStore docs
ViewWithStore's
lifecycle.When child store is used, view recieves composition of root and child state.
This allows View
to use both a local child state as well as global app's root state.
Child actions dispatching and state delivery works in the following way:
When action is dispatched to RootStore:
When action is dispatched to ChildStore:
let appState = AppState()
let storeFactory = StoreFactory<AppState, Action>(initialState: state, reducer: reducer)
let envStoreFactory = EnvStoreFactory(storeFactory: storeFactory)
UIHostingController(
rootView: ViewWithStoreFactory(envStoreFactory) {
ViewWithStore { stateComposition, dispatch in
FancyView(
state: substate,
dispatch: dispatch
)
}
.childStore(
initialState: ChildState(),
stateMapping: { appState, childState in
StateComposition(appState, childState)
},
reducer: { childState, action in childState.reduce(action) }
)
}
)
PureduxSwiftUI allows to add an extra presentation layer between view and state. It can be done for view reusability purposes. It also allows to improve performance by moving props preparation to background queue.
We can add Props
:
struct FancyView: View {
let props: Props
var body: some View {
Text(props.title)
.onAppear { props.didAppear() }
}
}
extension FancyView {
struct Props {
let title: String
let didAppear: Command
}
}
Props
can be though of as a view model.
Props
.
extension FancyView.Props {
static func makeProps(
state: AppState,
dispatch: @escaping Dispatch<Action>) -> FancyView.Props {
//prepare props for your fancy view
}
}
Props
:
ViewWithStore(props: FancyView.Props.makeProps) { props in
FancyView(
props: props
)
}
This allows to make Views dependent only on Props
and reuse it in different ways.
Props
preparation.ViewWithStore {
//Your content here
}
.usePresentationQueue(.main)
or standalone queue:
let queue = DispatchQueue(label: "some.queue", qos: .userInteractive)
ViewWithStore {
//your content here
}
.usePresentationQueue(.serialQueue(queue))
Equating<State>
guy:ViewWithStore {
//Your content here
}
.removeStateDuplicates(.equal { $0.title })
Props
only when it's necessary.Equating<State>
guy?Equatable
implementation won't work here.VStack {
ViewWithStore { state, dispatch in
FancyTitleView(state: state, dispatch: dispatch)
)
.removeStateDuplicates(.equal { $0.title })
ViewWithStore { state, dispatch in
FancySubtitleView(state: state, dispatch: dispatch)
)
.removeStateDuplicates(.equal { $0.subtitle })
}
Equating<State>
details ?Here is the definition:
Equating<T> { (lhs: T, rhs: T) -> Bool
//compare here
}
It has handy extensions, like Equating.alwaysEqual
or Equating.neverEqual
as well as &&
operator:
ViewWithStore { state, dispatch in
FancyView(state: state, dispatch: dispatch)
)
.removeStateDuplicates(
.equal { $0.title } &&
.equal { $0.subtitle }
)
PureduxSwiftUI is licensed under MIT license.
link |
Stars: 11 |
Last commit: Yesterday |
This release contains
Swiftpack is being maintained by Petr Pavlik | @ptrpavlik | @swiftpackco | API | Analytics