const ADD = 'ADD';
const addMessage = message => {
return {
type: ADD,
message
};
}
const messageReducer=(previousState=[], action) => {
switch(action.type){
case ADD:
return [...previousState, action.message];
default:
return previousState;
}
}
const store = Redux.createStore(messageReducer)
And
function createStore (reducer) {
let state;
return {
getState() {
return state;
},
dispatch(action) {
state = reducer(state, action);
return action;
}
}
}
You know what the function addMessage does, so substitute it when you use it for its actual value. Can also subsitute of the ADD
for “ADD”, inlining that as well, and replace Redux.createStore
with the actual implementation.
// This will be the message:
// { type: "ADD", message: "hello" }
function messageReducer (previousState = [], action) {
switch(action.type){
case "ADD":
return [...previousState, action.message];
default:
return previousState;
}
}
function createStore (reducer) {
let state;
return {
getState() {
return state;
},
dispatch(action) {
state = reducer(state, action);
return action;
}
}
}
const store = createStore(messageReducer);
The value of store
is now an object with two functions attached to it, called getState
and dispatch
.
So I run
store.getState()
If I look at the definition of createStore
, what getState
does is just return the value of the variable state
in the createStore
function (this is a closure).
The value of that is undefined
-
function createStore (reducer) {
let state;
So store.getState()
returns undefined
†
So I then run
store.dispatch({ type: "ADD", message: "hello" })
Again looking at the definition of createStore
, what dispatch
does is set state
to the return value of running your reducer with the current value of state
and the argument given to dispatch
.
messageReducer(undefined, { type: "ADD", message: "hello" })
In your messageReducer
function, if the value given as previousState
is undefined
, it defaults to an empty array, so it’s actually running
messageReducer([], { type: "ADD", message: "hello" })
Now you can check what happens in the switch statement: for the type of "ADD"
, you return a new array based on the current one, with the value of message
concatenated onto any current values. There aren’t any existing values, it’s an empty array. Return value is:
["hello"]
So back to createStore
's dispatch
function. That sets state
to ["hello"]
.
So then run store.getState()
again. This time the value of state
is not undefined. It’s
["hello"]
Dispatch again:
store.dispatch({type: "ADD", message: "hello again" })
Reducer runs, this time there’s a value to pass to previousState
. The type is "ADD"
, the message is "hello again"
, return value is
["hello", "hello again"]
If run getState
, that will be the result. And so on and so forth.
Technically it doesn’t really get much more complicated than that (with caveats, mainly related to doing asynchronous things, but the way asynchronous events are handled in redux is the same, but with an extra step).
It is basically the same as JS browser events: click, touch, drag etc. So like, in React you have
<button onClick={/* some function to run */}>
In plain JS,
myButton.addEventListener("click", /* some function to run */)
The event type is “click”
Actions in Redux are events (the name “action” is wrong, but the name “event” was already taken). You have a type of event, you check what the event is, you do something. And the something you do is return a new state for the app. It’s just done very explicitly, the machinery is fully exposed rather than hidden in the functionality provided by the browser.
In practice you want events for every possible action that you want to affect the state. So even in a small app you end up with lots of actions and state and complicated reducer logic to remake the state. These examples here are very very small, and seem almost pointless. But it’s impossible to explain how to use it by starting with complex examples.
† the state
variable in createStore
could be initialised like this, instead of being undefined:
let state = reducer(undefined, undefined);
Because the first argument (previousState
defaults to []
, and the second causes the switch to hit the default and just return previousState
, that would set the initial state to []
.
What Redux’ actual createStore
does is run dispatch
once before returning. Because the dispatch
returns the action, if development tools have been set up for Redux, a developer can then see in a console when the store has fully initialised.
There is also another function that createStore
returns, called addListener
, that I didn’t include. It’s also very simple. It takes a function as an argument. When addListener
is ran it adds that function to an array stored alongside state
in the store. Whenever `dispatch runs, it runs every function in the array before returning. This is how you get React to update whenever something is dispatched to update the state.