What is Store in Redux?
Redux is a predictable state container. It also needs to provide some way to change the state, subscribe some callback functions, or provides some observables. Store is there to serve for these purposes. For example:
store.getState() // => Get current state
store.dispatch(action) // => Dispatch action to change the state
store.subscribe(function) // => Subscribe to hook some callbacks after dispatching
store.replaceReducer(reducer) // => Replace reducer
Show me the code!
Let’s see the implementation of dispatch for example:
export default function createStore(reducer, preloadedState, enhancer) {
var currentReducer = reducer
var currentState = preloadedState
var currentListeners = []
var nextListeners = currentListeners
var isDispatching = false
function dispatch(action) {
if (!isPlainObject(action)) {
throw new Error(
'Actions must be plain objects. ' +
'Use custom middleware for async actions.'
)
}
if (typeof action.type === 'undefined') {
throw new Error(
'Actions may not have an undefined "type" property. ' +
'Have you misspelled a constant?'
)
}
try {
isDispatching = true
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
var listeners = currentListeners = nextListeners
for (var i = 0; i < listeners.length; i++) {
var listener = listeners[i]
listener()
}
return action
}
return {
dispatch
}
}
What things are going on is quite straightforward here: First, validate action object; Then, pass state and action into reducer, and save the returned state; Finally, trigger every listener in the store, and return action. Done!
And again, let’s use ruby to implement this (only essential parts):
module Rubidux
class Store
attr_accessor :state
attr_accessor :reducer
attr_accessor :listeners
attr_accessor :dispatch
attr_accessor :subscribe
def initialize(reducer, preload_state, enhancer = nil)
raise ArgumentError.new("Expect reducer to be a Proc.") unless reducer.is_a? Proc
enhancer(Rebidux::Store).(reducer, preload_state) if enhancer
@state = preload_state || {}
@listeners = []
@reducer = reducer
@dispatch = _dispatch
@subscribe = _subscribe
end
private
def _dispatch
-> (action) {
raise ArgumentError.new("Expect action to have key 'type'.") unless action[:type]
@state = @reducer.(@state, action)
@listeners.each(&:call)
action
}
end
def _subscribe
-> (listener) {
raise ArgumentError.new("Expect listener to be a Proc.") unless listener.is_a? Proc
@listeners.push listener
-> { @listeners.delete listener }
}
end
end
end
Ruby is amazingly elegant!