Composable Arsitektur
Your chapter in my book is over, It will forever be one of my favorites but I cant keep rereading it hoping for different ending :) ~ Thank you 🙏
Pada composable arsitektur tidak bisa lepas dari Reducer, Action dan State. Untuk masuk lebih dalam ke composable arsitektur kita perlu tau apa itu Reducer, Action dan State.
Reducer
Reducers are functions that take the current
state
and anaction
as arguments, and return a newstate
result. In other words,(state, action) => newState
. (Redux)
Inti-nya Reducer berfungsi untuk mengubah current input (state dan action) menjadi state baru.
Action
Actions are plain JavaScript objects that have a
type
field. As mentioned earlier, you can think of an action as an event that describes something that happened in the application. (Redux)
Action mendiskripsikan apa yang terjadi pada aplikasi, bisa kita sebut juga user action, dimana user melakukan click, tap, hold, double tap etc.
State
One of the core principles of React and Redux is that your UI should be based on your state. So, one approach to designing an application is to first think of all the state needed to describe how the application works. It’s also a good idea to try to describe your UI with as few values in the state as possible, so there’s less data you need to keep track of and update. (Redux)
Jangan lupa juga
Store
The Redux store brings together the state, actions, and reducers that make up your app. The store has several responsibilities:
Holds the current application state inside
Allows access to the current state via
store.getState()
;Allows state to be updated via
store.dispatch(action)
;Registers listener callbacks via
store.subscribe(listener)
;Handles unregistering of listeners via the
unsubscribe
function returned bystore.subscribe(listener)
.It’s important to note that you’ll only have a single store in a Redux application. When you want to split your data handling logic, you’ll use reducer composition and create multiple reducers that can be combined together, instead of creating separate stores.
class Store<S, A> {
private let reducer: Reducer<S, A>
private var subscribers: [(S) -> Void] = []
private var currentState: S {
didSet {
self.subscribers.forEach { $0(self.currentState) }
}
} init(reducer: Reducer<S, A>, inititalState: S) {
self.reducer = reducer
self.currentState = inititalState
} func dispatch(_ action: A) {
self.reducer.reduce(&self.currentState, action)
} func subscribe(_ subscriber: @escaping (S) -> Void) {
self.subscribers.append(subscriber)
subscriber(self.currentState)
}
}
Prisip Composable arsitektur adalah UI harus berdasarkan state. Jadi untuk mendesain app pertama kita harus berpikir state apa saja yang ada dan dibutuhkan untuk mendeskripsikan bagaimana aplikasi bekerja.
Masuk ke code, jadi Reducer meng-implement Monoid atau Monads. Jadi Reducer bersifat asosiatif. Sesuai dengan materi sebelumnya Reducer bisa kita combine dengan reducer lain sehingga menjadi satu Reducer baru.
struct Reducer<S, A> {
let reduce: (inout S, A) -> Void
}extension Reducer: Monoid {
static var e: Reducer<S, A> {
return Reducer { s, _ in return }
}
static func <> (lhs: Reducer<S, A>, rhs: Reducer<S, A>) -> Reducer<S, A> {
return Reducer { s, a in
lhs.reduce(&s, a)
rhs.reduce(&s, a)
}
}
}
Untuk menggabungkan dua Reducer yang memiliki State dan Action yang berbeda kita harus menggunakan pendekatan baru, yaitu menggunakan function Lift untuk menyamakan mereka. Contoh kita memiliki accountReducer
let accountReducer = Reducer<AccountState, AccountAction> { state, action in
switch action {
case let .login(user):
state.logedInUser = user
case let .tappedEpisodes(episode):
state.watchedEpisodes.append(episode)
case .tappedLogout:
state.logedInUser = nil
case .tappedNotification(on: let on):
state.settings.notificationOn = on
}
}
dan EpisodeReducer seperti berikut
let episodeReducer = Reducer<EpisodesState, EpisodeAction> { state, action in
switch action {
case let .tappedEpisodes(episode):
state.watchedEpisodes.append(episode)
}
}
kita tidak bisa menggabungkan nya secara langsung karena
Reducer<AccountState, AccountAction> != Reducer<EpisodesState, EpisodeAction>
Jadi kita harus membuat Translator sehingga kedua Reducer tetap bisa berkomunikasi satu sama lain dengan cara kita haru melifting kedua Action dari Reducer menjadi AppAction dan State menjadi AppState,
struct AppState {
var episodeState: EpisodesState = .init()
var accountState: AccountState = .init()
}enum AppAction {
case accountAction(AccountAction)
case episodeAction(EpisodeAction)
}
Jadi bagaimana proses lifting nya
extension Reducer {
func lift<T, B>(state: WritableKeyPath<T, S>, action: Prism<B,A>) -> Reducer<T, B> {
return Reducer<T, B> { t, b in
guard let a = action.tryGet(b) else { return }
self.reduce(&t[keyPath: state], a)
}
}
}
Sehingga resultnya akan menjadi
let liftedEpisodeReducer = episodeReducer.lift(state: \AppState.episodeState, action: AppAction.prism.episodeAction)let liftedAccountReducer = accountReducer.lift(state: \AppState.accountState, action: AppAction.prism.accountAction)
Dimana liftedEpisodeReducer dan liftedAccountReducer adalah Reducer<AppState, AppAction> sehingga kita bisa mengcombine keduanya, menjadi seperti berikut
let appReducer: Reducer<Appstate, AppAction> = liftedEpisodeReducer <> liftedAccountReducer
Untuk Full Source bisa di check dibawah.
Its better if we know what is going on on our code than we just use other person code without knowing what is behind
Jadi usahakan kita tau secara mengakar (secara Radikal) tentang apa yang kita kerjakan sehingga kita bisa dengan mudah mencari sumber kesalahan pada kode kita.