Last active
June 18, 2022 10:18
-
-
Save cooler333/4d2760054fe33615ac2a9de476af7ae4 to your computer and use it in GitHub Desktop.
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
// Swift Playground example of Redux JS framework | |
import Foundation | |
import PlaygroundSupport | |
// MARK: Action | |
public protocol Action { | |
associatedtype T = Any | |
} | |
// MARK: Dispatch | |
typealias Dispatch<Action> = (_ action: Action) -> Action | |
// MARK: Reducer | |
typealias Reducer<S, Action> = (_ state: S, _ action: Action) -> S | |
// MARK: MiddlewareAPI | |
struct MiddlewareAPI<S, Action> { | |
let dispatch: (Action) -> Action | |
let getState: () -> S | |
} | |
// MARK: Middleware | |
typealias Middleware<S, Action> = (_ api: MiddlewareAPI<S, Action>) -> ((_ next: @escaping Dispatch<Action>) -> ((_ action: Action) -> Action)) | |
// MARK: Store | |
struct Store<S, Action> { | |
let dispatch: Dispatch<Action> | |
let getState: () -> S | |
} | |
// MARK: StoreEnhancerStoreCreator | |
typealias StoreEnhancerStoreCreator<S, Action> = (_ reducer: @escaping Reducer<S, Action>, _ preloadState: S) -> Store<S, Action> | |
// MARK: Enhancer | |
typealias StoreEnhancer<S, Action> = (_ next: @escaping StoreEnhancerStoreCreator<S, Action>) -> StoreEnhancerStoreCreator<S, Action> | |
// MARK: createStore | |
func createStore<S, Action>( | |
reducer: @escaping Reducer<S, Action>, | |
preloadedState: S, | |
enhancer: StoreEnhancer<S, Action>? = nil | |
) -> Store<S, Action> { | |
if let enhancer = enhancer { | |
return enhancer { reducer, preloadState in | |
return createStore(reducer: reducer, preloadedState: preloadedState) | |
}(reducer, preloadedState) | |
} | |
let currentReducer = reducer | |
var currentState = preloadedState | |
func getState() -> S { | |
return currentState | |
} | |
func dispatch(action: Action) -> Action { | |
currentState = currentReducer(currentState, action) | |
return action | |
} | |
return Store<S, Action>(dispatch: dispatch, getState: getState) | |
} | |
// MARK: applyMiddleware | |
func applyMiddleware<S, Action>(middlewares: Middleware<S, Action>...) -> StoreEnhancer<S, Action> { | |
return { next -> StoreEnhancerStoreCreator in | |
return { reducer, preloadState -> Store<S, Action> in | |
let store = createStore(reducer: reducer, preloadedState: preloadState) | |
var dispatch: Dispatch<Action>! | |
let middlewareAPI: MiddlewareAPI = MiddlewareAPI( | |
dispatch: { (action: Action) in | |
dispatch(action) | |
}, | |
getState: store.getState | |
) | |
let chain = middlewares.map { middleware in middleware(middlewareAPI) } | |
dispatch = compose(chain)(store.dispatch) | |
return Store<S, Action>(dispatch: dispatch, getState: store.getState) | |
} | |
} | |
} | |
// MARK: compose | |
func compose<Action>(_ funcs: [(@escaping Dispatch<Action>) -> ((Action) -> Action)]) -> (@escaping Dispatch<Action>) -> ((Action) -> Action) { | |
if funcs.count == 1 { | |
return funcs.first! | |
} | |
return funcs[1...].reduce(funcs.first!) { a, b in | |
return { action in | |
a(b(action)) | |
} | |
} | |
} | |
// MARK: Test | |
enum FooAction: Action { | |
case foo(text: String) | |
case bar(count: Int) | |
case receive(value: String) | |
} | |
let reducer: Reducer<String, FooAction> = { state, action in | |
switch action { | |
case let .foo(text): | |
return state + ", " + text | |
case let .bar(count): | |
return state + ", " + "\(count)" | |
case let .receive(value): | |
return state + ", " + "async \(value)" | |
} | |
} | |
let loggerFoo: Middleware<String, FooAction> = { (store) in | |
return { next in | |
return { action in | |
print("[Foo] will dispatch action:", action) | |
let returnValue = next(action) | |
print("[Foo] state after dispatch:", store.getState()) | |
return returnValue | |
} | |
} | |
} | |
let loggerBar: Middleware<String, FooAction> = { (store) in | |
return { next in | |
return { action in | |
print("[Bar] will dispatch action:", action) | |
let returnValue = next(action) | |
print("[Bar] state after dispatch:", store.getState()) | |
return returnValue | |
} | |
} | |
} | |
let asyncMiddleware: Middleware<String, FooAction> = { (store) in | |
return { next in | |
return { action in | |
if case let .foo(value) = action { | |
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { | |
_ = store.dispatch(.receive(value: "valueFromAPI(\(value))")) | |
} | |
} | |
return next(action) | |
} | |
} | |
} | |
let store: Store<String, FooAction> = createStore( | |
reducer: reducer, | |
preloadedState: "initial string", | |
enhancer: applyMiddleware(middlewares: loggerFoo, loggerBar, asyncMiddleware) | |
) | |
store.dispatch(.foo(text: "one")) | |
store.dispatch(.bar(count: 2)) | |
store.dispatch(.foo(text: "three")) | |
print(store.getState()) | |
DispatchQueue.main.asyncAfter(deadline: .now() + 2) { | |
print("2 seconds gone") | |
print(store.getState()) | |
} | |
PlaygroundPage.current.needsIndefiniteExecution = true | |
// Result: | |
// | |
// [Foo] will dispatch action: foo(text: "one") | |
// [Bar] will dispatch action: foo(text: "one") | |
// [Bar] state after dispatch: initial string, one | |
// [Foo] state after dispatch: initial string, one | |
// [Foo] will dispatch action: bar(count: 2) | |
// [Bar] will dispatch action: bar(count: 2) | |
// [Bar] state after dispatch: initial string, one, 2 | |
// [Foo] state after dispatch: initial string, one, 2 | |
// [Foo] will dispatch action: foo(text: "three") | |
// [Bar] will dispatch action: foo(text: "three") | |
// [Bar] state after dispatch: initial string, one, 2, three | |
// [Foo] state after dispatch: initial string, one, 2, three | |
// initial string, one, 2, three | |
// [Foo] will dispatch action: receive(value: "valueFromAPI(one)") | |
// [Bar] will dispatch action: receive(value: "valueFromAPI(one)") | |
// [Bar] state after dispatch: initial string, one, 2, three, async valueFromAPI(one) | |
// [Foo] state after dispatch: initial string, one, 2, three, async valueFromAPI(one) | |
// [Foo] will dispatch action: receive(value: "valueFromAPI(three)") | |
// [Bar] will dispatch action: receive(value: "valueFromAPI(three)") | |
// [Bar] state after dispatch: initial string, one, 2, three, async valueFromAPI(one), async valueFromAPI(three) | |
// [Foo] state after dispatch: initial string, one, 2, three, async valueFromAPI(one), async valueFromAPI(three) | |
// 2 seconds gone | |
// initial string, one, 2, three, async valueFromAPI(one), async valueFromAPI(three) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment