diff --git a/CHANGELOG.md b/CHANGELOG.md index d33bac6..711e6de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,8 @@ #Upcoming Release +**Breaking API Changes:** + +- Change Reducer to a generic function type - @Qata + **API Changes:** - Rename `Middleware.increase(_:)` to `Middleware.flatMap(_:)` - @Qata diff --git a/README.md b/README.md index 0d70f61..b93814b 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ enum AppAction: Action { Your reducer needs to respond to these different actions, that can be done by switching over the value of action: ```swift -let appReducer = Reducer { action, state in +let appReducer: Reducer = { action, state in switch action as? AppAction { case .Increase?: state.counter += 1 @@ -69,7 +69,7 @@ let appReducer = Reducer { action, state in } ``` -A single `Reducer` should only deal with a single field of the state struct. You can chain together multiple reducers using `Reducer(firstReducer, secondReducer, ...)`. +A single `Reducer` should only deal with a single field of the state struct. You can nest multiple reducers within your main reducer to provide separation of concerns. In order to have a predictable app state, it is important that the reducer is always free of side effects, it receives the current app state and an action and returns the new app state. diff --git a/Sources/CoreTypes/Reducer.swift b/Sources/CoreTypes/Reducer.swift index 5709b26..9d8d43b 100644 --- a/Sources/CoreTypes/Reducer.swift +++ b/Sources/CoreTypes/Reducer.swift @@ -8,41 +8,4 @@ import Foundation -/** - Reducer is a structure that allows you to modify the current state - by reducing actions. - */ -public struct Reducer { - public let transform: (Action, State) -> State - - /** - Initialises the `Reducer` with a transformative function. - - - parameter transform: The function that will be able to modify passed state. - */ - public init(_ transform: @escaping (Action, State) -> State) { - self.transform = transform - } - - /** - Initialises the `Reducer` by concatenating the transformative functions from - the `Reducer`s that were passed in. - */ - public init(_ first: Reducer, _ rest: Reducer...) { - self = rest.reduce(first) { - $0.concat($1) - } - } - - /// Concatenates the transform function of the passed `Reducer` onto the callee's transform. - public func concat(_ other: Reducer) -> Reducer { - return map(other.transform) - } - - /// Concatenates the transform function onto the callee's transform. - public func map(_ transform: @escaping (Action, State) -> State) -> Reducer { - return Reducer { - return transform($0, self.transform($0, $1)) - } - } -} +public typealias Reducer = (_ action: Action, _ state: State) -> State diff --git a/Sources/CoreTypes/Store.swift b/Sources/CoreTypes/Store.swift index 8f76047..5ee0885 100644 --- a/Sources/CoreTypes/Store.swift +++ b/Sources/CoreTypes/Store.swift @@ -18,7 +18,7 @@ open class Store where ObservablePro private let reducer: StoreReducer private let disposeBag = SubscriptionReferenceBag() - public required init(reducer: StoreReducer, observable: ObservableProperty, middleware: StoreMiddleware = Middleware()) { + public required init(reducer: @escaping StoreReducer, observable: ObservableProperty, middleware: StoreMiddleware = Middleware()) { self.reducer = reducer self.observable = observable self.middleware = middleware @@ -30,7 +30,7 @@ open class Store where ObservablePro actions.forEach { self?.dispatch($0) } } middleware.transform({ self.observable.value }, dispatchFunction, action).forEach { action in - observable.value = reducer.transform(action, observable.value) + observable.value = reducer(action, observable.value) } } } diff --git a/Tests/Observable/StoreTests.swift b/Tests/Observable/StoreTests.swift index 5bdf5af..a200d62 100644 --- a/Tests/Observable/StoreTests.swift +++ b/Tests/Observable/StoreTests.swift @@ -36,7 +36,7 @@ class DeInitStore: Store> { deInitAction?() } - convenience init(reducer: Reducer.ValueType>, + convenience init(reducer: @escaping Reducer.ValueType>, observable: ObservableProperty, middleware: Middleware.ValueType> = Middleware(), deInitAction: @escaping () -> Void) { @@ -46,7 +46,7 @@ class DeInitStore: Store> { self.deInitAction = deInitAction } - required init(reducer: Reducer.ValueType>, + required init(reducer: @escaping Reducer.ValueType>, observable: ObservableProperty, middleware: Middleware.ValueType>) { super.init(reducer: reducer, diff --git a/Tests/ReducerTests.swift b/Tests/ReducerTests.swift index 4ca5cb7..de9c1bb 100644 --- a/Tests/ReducerTests.swift +++ b/Tests/ReducerTests.swift @@ -15,18 +15,18 @@ class MockReducerContainer { var reducer: Reducer! init() { - reducer = Reducer { action, state in + reducer = { action, state in self.calledWithAction.append(action) return state } } } -let increaseByOneReducer = Reducer { action, state in +let increaseByOneReducer: Reducer = { action, state in CounterState(count: state.count + 1) } -let increaseByTwoReducer = Reducer { action, state in +let increaseByTwoReducer: Reducer = { action, state in CounterState(count: state.count + 2) } @@ -39,9 +39,11 @@ class ReducerTests: XCTestCase { let mockReducer1 = MockReducerContainer() let mockReducer2 = MockReducerContainer() - let combinedReducer = Reducer(mockReducer1.reducer, mockReducer2.reducer) + let combinedReducer: Reducer = { action, state in + mockReducer2.reducer(action, mockReducer1.reducer(action, state)) + } - _ = combinedReducer.transform(NoOpAction(), CounterState()) + _ = combinedReducer(NoOpAction(), CounterState()) XCTAssertEqual(mockReducer1.calledWithAction.count, 1) XCTAssertEqual(mockReducer2.calledWithAction.count, 1) @@ -53,9 +55,11 @@ class ReducerTests: XCTestCase { it combines the results from each individual reducer correctly */ func testCombinesReducerResults() { - let combinedReducer = Reducer(increaseByOneReducer, increaseByTwoReducer) + let combinedReducer: Reducer = { action, state in + increaseByTwoReducer(action, increaseByOneReducer(action, state)) + } - let newState = combinedReducer.transform(NoOpAction(), CounterState()) + let newState = combinedReducer(NoOpAction(), CounterState()) XCTAssertEqual(newState.count, 3) } diff --git a/Tests/TestFakes.swift b/Tests/TestFakes.swift index 0854f89..1a7be1d 100644 --- a/Tests/TestFakes.swift +++ b/Tests/TestFakes.swift @@ -67,7 +67,7 @@ struct SetValueStringAction: StandardActionConvertible { } -let testReducer = Reducer { action, state in +let testReducer: Reducer = { action, state in switch action { case let action as SetValueAction: return TestAppState(testValue: action.value) @@ -76,7 +76,7 @@ let testReducer = Reducer { action, state in } } -let testValueStringReducer = Reducer { action, state in +let testValueStringReducer: Reducer = { action, state in switch action { case let action as SetValueStringAction: return TestStringAppState(testValue: action.value)