Demystifying Redux pt1
September 4, 2016
Redux is a predictable state container for JavaScript apps. It borrow ideas from both the flux architecture and the elm architecture. Shortly:
state = f(state, action)
This article, is part of a series of posts aiming to explain redux architecture and its internals by reimplementing parts of it (Disclaimer: naive/not-optimized implementation ahead). We’ll start from the core, main logic: createStore
createStore - the redux state container
By a quick look at the official README, the gist showing the basic concepts of redux purely rely on createStore:
As the name suggest it creates a Redux store holding the state of the app. Its API is { subscribe, dispatch, getState }. It requires a reducer as its only mandatory argument:
This give us enough information to start writing some code.
Now that we have a skeleton in place lets implement the createStore APIs.
getState()
Not much to be said about this method, it will simply return the enclosed state:
dispatch()
This is how redux mutate the state. By dispatching an action (nothing more then a simple POJO) that express the mutation intent. (In this regards I prefer how the elm architecture call it a message instead of an action, as it just carry the information but doesn’t perform the actual action itself’)
The dispatch signature will then look something like:
So if the dispatch method take an action as its only argument, and that action is nothing more then an object carrying some informations, you’ll probably be wondering how is the state going to be mutated… Say hello to the reducer! The role of the reducer is to reduce the state tree to a new tree with the applied transformation in place, aka: mutate the state.
reducer = (state, action) => state
Pretty simple eh? We stated earlier that createStore require only a reducer as mandatory parameter, so, as it is already defined we can simply use it inside our method:
Et voila! the store dispatcher now does what it says: dispatches an action to the reducer. The reducer will analyse the message (action) and purely mutate the state accordingly, returning the new state.
subscribe()
Up to this point we are able to get the state from our store by calling getState() and we are able to mutate the state by dispatching actions via dispatch(). To get automatically informed about this changes we need a subscription logic. Subscribe will indeed allows us to register listeners so that each time an action get dispatched, and most probably the state get mutated all the subscribed clients (our listeners) will be invoked. On subscribing we will return a dispose function allowing us to unsubscribe.
subscribe = listener => {dispose}
First lets add a container to hold the active listeners, and the relative logic to add/remove them:
We now need to trigger our listeners once a dispatch occurs:
Initialize the store
Before exposing the API’s we’ll now just need to initialise the store, by dispatching an init action, that will set the initial state of the store.
Conclusions
That’s it, you can now create a store, dispatch actions to mutate it and have all the subscribed clients be notified about any changes. This is the main overall architecture of redux. Simple and elegant.
Our final implementation of createStore:
There are some more complex nuances like applyMiddleware, combineReducers and finally how redux can be used with a view layer like react (think react = state => f(state)) but I’ll cover those in the upcoming articles.
You can now follow the basic gist in the official redux documentation and it should work as expected:
Finally we want to be able to specify an initial state when instantiating our store. Simple: move the state definition from the body of the function to its signature:
If you want to play around, feel free to fork my redux-playground repository