Issue #502
Mutation
is used to mutate state synchronously. Action
is like intent, either from app or from user action. Action
maps to Mutation
in form of Publisher
to work with async action, similar to redux-observable
AnyReducer
is a type erasure that takes the reduce
function
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 import Combineimport Foundationpublic protocol Reducer { associatedtype State associatedtype Mutation func reduce (state: State, mutation: Mutation) -> State } public struct AnyReducer <State , Mutation > { public let reduce : (State , Mutation ) -> State public init <R : Reducer >(reducer: R ) where R .State == State , R .Mutation == Mutation { self .reduce = reducer.reduce } } public protocol Action { associatedtype Mutation func toMutation () -> AnyPublisher <Mutation , Never > } public final class Store <State , Mutation >: ObservableObject { @Published public private (set ) var state: State public let reducer: AnyReducer <State , Mutation > public private (set ) var cancellables = Set <AnyCancellable >() public init (initialState: State , reducer: AnyReducer <State , Mutation >) { self .state = initialState self .reducer = reducer } public func send <A: Action>(action: A) where A .Mutation == Mutation { action .toMutation() .receive(on: DispatchQueue .main) .sink(receiveValue: update(mutation:)) .store(in : &cancellables) } public func update (mutation: Mutation) { self .state = reducer.reduce (state, mutation) } }
To use, conform to all the protocols. Also make typelias AppStore
in order to easy specify type in SwiftUI View
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 import SwiftUIimport Combinetypealias AppStore = Store <AppState , AppMutation >let appStore: AppStore = AppStore ( initialState: AppState (), reducer: appReducer ) struct AppState : Codable { var hasShownOnboaring = false } struct AppReducer : Reducer { func reduce (state: AppState, mutation: AppMutation) -> AppState { var state = state switch mutation { case .finishOnboarding: state.hasShownOnboaring = true @unknown default : break } return state } } enum AppMutation { case finishOnboarding }
Use in SwiftUI
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 struct RootScreen : View { @EnvironmentObject var store: AppStore var body: some View { if store.state.hasShownOnboaring { return Text ("Welcome" ) .eraseToAnyView() } else { return OnboardingScreen () .eraseToAnyView() } } } struct OnboardingScreen : View { @EnvironmentObject var store: AppStore private func done () { store.send(action: AppAction .finishOnboarding) } }
Reference