Last active
August 6, 2019 05:27
-
-
Save Qata/d6b8f62ff12fdf948f3520f6fe6d2081 to your computer and use it in GitHub Desktop.
Recombine
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
Middleware is a structure that allows you to modify, filter out and dispatch more | |
actions, before the action being handled reaches the store. | |
*/ | |
public struct Middleware<State, Action> { | |
typealias Transform = (State, Action) -> [Action] | |
internal let transform: (State, Action) -> [Action] | |
/// Create a blank slate Middleware. | |
public init() { | |
self.transform = { [$1] } | |
} | |
/** | |
Initialises the middleware with a transformative function. | |
- parameter transform: The function that will be able to modify passed actions. | |
*/ | |
internal init(_ transform: @escaping Transform) { | |
self.transform = transform | |
} | |
/** | |
Initialises the middleware by concatenating the transformative functions from | |
the middleware that was passed in. | |
*/ | |
public init(_ first: Middleware, _ rest: Middleware...) { | |
self = .init(first, rest) | |
} | |
/** | |
Initialises the middleware by concatenating the transformative functions from | |
the middleware that was passed in. | |
*/ | |
public init<S: Sequence>(_ first: Middleware, _ rest: S) where S.Element == Middleware { | |
self = rest.reduce(first) { | |
$0.concat($1) | |
} | |
} | |
/// Safe encapsulation of side effects guaranteed not to affect the action being passed through the middleware. | |
public func sideEffect(_ effect: @escaping (State, Action) -> Void) -> Middleware { | |
.init { state, action in | |
self.transform(state, action).map { | |
effect(state, $0) | |
return $0 | |
} | |
} | |
} | |
/// Concatenates the transform function of the passed `Middleware` onto the callee's transform. | |
public func concat(_ other: Middleware) -> Middleware { | |
.init { state, action in | |
self.transform(state, action).flatMap { | |
other.transform(state, $0) | |
} | |
} | |
} | |
/// Transform the action into another action. | |
public func map(_ transform: @escaping (State, Action) -> Action) -> Middleware { | |
.init { state, action in | |
self.transform(state, action).map { | |
transform(state, $0) | |
} | |
} | |
} | |
/// One to many pattern allowing one action to be turned into multiple. | |
public func flatMap<S: Sequence>(_ transform: @escaping (State, Action) -> S) -> Middleware where S.Element == Action { | |
.init { state, action in | |
self.transform(state, action).flatMap { | |
transform(state, $0) | |
} | |
} | |
} | |
/// Filters while mapping actions to new actions. | |
public func filterMap(_ transform: @escaping (State, Action) -> Action?) -> Middleware { | |
.init { state, action in | |
self.transform(state, action).compactMap { | |
transform(state, $0) | |
} | |
} | |
} | |
/// Drop the action iff `isIncluded(action) != true`. | |
public func filter(_ isIncluded: @escaping (State, Action) -> Bool) -> Middleware { | |
.init { state, action in | |
self.transform(state, action).filter { | |
isIncluded(state, $0) | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public struct Reducer<State, Action> { | |
public typealias Transform = (_ state: State, _ action: Action) -> State | |
let transform: Transform | |
public init(_ transform: @escaping Transform) { | |
self.transform = transform | |
} | |
public static func concat(_ first: Reducer<State, Action>, _ rest: Reducer<State, Action>...) -> Reducer<State, Action> { | |
return concat(first, rest) | |
} | |
public static func concat<S>(_ first: Reducer<State, Action>, _ rest: S) -> Reducer<State, Action> where S: Sequence, S.Element == Reducer<State, Action> { | |
return .init { state, action in | |
rest.reduce(first.transform(state, action)) { state, reducer in | |
reducer.transform(state, action) | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Combine | |
open class Store<State, Action>: ObservableObject, Subscriber { | |
@Published public private(set) var state: State | |
private let actions = PassthroughSubject<Action, Never>() | |
private var cancellables = Set<AnyCancellable>() | |
public required init(state: State, reducer: Reducer<State, Action>, middleware: Middleware<State, Action> = .init()) { | |
self.state = state | |
actions.scan(state) { state, action in | |
middleware | |
.transform(state, action) | |
.reduce(state, reducer.transform) | |
} | |
.sink(receiveValue: { [unowned self] state in | |
self.state = state | |
}) | |
.store(in: &cancellables) | |
} | |
open func dispatch(_ actions: Action...) { | |
dispatch(actions) | |
} | |
open func dispatch<S: Sequence>(_ actions: S) where S.Element == Action { | |
actions.forEach(self.actions.send) | |
} | |
public func receive(subscription: Subscription) { | |
subscription.store(in: &cancellables) | |
subscription.request(.unlimited) | |
} | |
public func receive(_ input: Action) -> Subscribers.Demand { | |
dispatch(input) | |
return .unlimited | |
} | |
public func receive(completion: Subscribers.Completion<Never>) {} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment