Redux adopted a handful of constraints from the Flux architecture: Actions encapsulate information for reducer to update the state deterministically. It has a singleton Store, indeed. Single Flux dispatcher is replaced with multiple small Reducers that pick up information from actions and "reduce" it ti a new state that's then saved in the Store. When state in the Store is changed the View as per subscription receives props.
Concept:
View -> Action -> Reducer -> Store -> View
The name "Redux", therefore, combines two "Reduce" and "Flux". Difference is state does not live in the View anymore, it is only connected to the View, meaning both ends (the View and the Store) are now responsible for Action-Update-Notification uni-directional wheel.
Notably, Redux can run standalone!
Action
Redux loves objects. And Actions are objects, indeed. It has an action type and payload.
{
type: 'set_thermostate_temp',
payload: {
id: 'basement',
temprature: 24.0,
authorizedBy: 'Timofei Shchepkin'
}
}
Executing this action will prompt dispatch that may alter (or not) state in the Store.
Reducer
In unidirectional data flow, the View dispatches actions to reducer.
By definition from functional programming, reducer is a pure function, hence produces deterministic results with no side effects.
Reducer has two inputs: state and action. State is the whole state object from the Store, the action is the dispatched object with type and optional payload.
The Reducer reduces the Store.
(state, action) => newState
Moreover, it harnesses immutability of the state inside Store. The following is incorrect, since it would mutate the previous state
(state, action) => state.push(action.payload)
The following is allowed, due to concat
which concatenates immutable store to the payload.
(state, action) => state.concat(action.payload)
Action type
I haven't mentioned type just yet. The story is when action object arrives at reducer's land, as if by a switch
statement, the action type becomes the key to a lock (the correct reducer). If key didn't open any doors, well, state persists untouched.
Example of a Reducer Land
function reducer(state, action) {
switch(action.type) {
case: 'set_thermostate_temp': {
//do some magic IOT stuff
}
case: 'toggle_window_shutters': {
return state.map(windows => window.id === action.id
? Object.assign({}, windows, {opened: !window.opened})
: windows
);
}
default: return state;
}
}
In open_window_shutters
case, map
is used to iterate over controllable windows in the basement, and toggles uniquely identified instance on
Functionality of map
always returns new array, it does not mutate the previous state.
Object.assign()
returns a new object without mutating the old object, under the hood it merges all given objects from the former to the latter into each other. If a former object shares the same property as a latter object, the property of the latter object will be used. Thus, the completed property of the updated window
item will be the negated state of the old window
item.
Noteworthy, actions and reducers are plain JS, no Redux magic involved so far.
In addition, stay cool, dry - use antiperspirant - extract case branches into pure functions
function reducer(state, action) {
switch(action.type) {
case: 'set_thermostate_temp': {
return applyThermostateTemp(state, action);
}
case: 'toggle_window_shutters': {
return applyWindowToggler(state, action);
}
default: return state;
}
}
function applyThermostateTemp(s, a) {
return state.thermostate.currentTemperature = a.temp; // IOT ~~magic~~
}
function applyWindowToggler {
return state.map(windows => window.id === action.id
? Object.assign({}, windows, {opened: !window.opened})
: windows
);
}
Store
Singleton store holds one global state object. Store delegates actions to the reducer. Store triggers actions. Store updates the state and notifies subscribed components. And, en fin, it is the first library dependency to be encountered in Redux.
import { createStore } from 'redux'
To create a singleton instance, pass the mandatory argument - reducer
and optional initial state
const store = createStore(reducer, {})
Dispatching an action
store.dispatch(action)
Reading global state from the Store
store.getState()
Subscribe (and unsubscribe) to the Store in order to listen for updates
const unsubscribe = store.subscribe(() => {
console.log(store.getState());
});
// eventually unsubscribe
unsubscribe();
Conclusion
You know about all the basics in Redux now. A view dispatches an action on the store, the action passes all reducers and gets reduced by reducers that care about it. The store saves the new state object. Finally, a listener updates the view with the new state.
Additional material
https://facebook.github.io/flux/
https://youtu.be/nYkdrAPrdcw?list=PLb0IAmt7- GS188xDYE- u1ShQmFFGbrk0v
https://twitter.com/dan_abramov
https://twitter.com/acdlite
https://www.youtube.com/watch?v=xsSnOQynTHs
https://www.youtube.com/watch?v=uvAXVMwHJXU
Top comments (4)
The irony of redux is that it is very hard to reduce the explanation of how it works.
Nevertheless, it should be said that implementation is rather trivial
Yes, it’s very reduced’ in size and complexity.
I will stop now... :D
Thanks ...really helpful article
The flowchart solely explains how redux works.