The first principle of Redux:
The entire state of the application will be represented by one JavaScript object.
This holds true regardless of the app's complexity.
All mutations and changes to the state in Redux are explicit.
Everything that changes in the application, including the data and the UI state, is contained in a single object called the state or state tree.
Since the entire state is represented in a single object, we are able to keep track of changes over time.
State in Todo App
"current_state:"
[object Object] {
todos: [[object Object] {
completed: true,
id: 0,
text: "hey",
}, [object Object] {
completed: false,
id: 1,
text: "ho",
}],
visibilityFilter: "SHOW_ACTIVE"
}
The second principle of Redux is that the state tree is read only.
You cannot modify or write to it.
Any time you want to change the state, you have to dispatch an action. An action is a plain JS object describing the change. Just like the state is the minimal representation of your app's data, the action is the minimal representation of the change to that data.
There's no set structure for the action object. There's only one requirement: it needs a type property (conventionally a String, since they can be serialized).
For example, a counter app can contain INCREMENT
and DECREMENT
actions that adjust its count. In the case of a ToDo app, the display components don't know how an item was added to the list — all they know is that an ADD_TODO
action was dispatched, with text
content "hey" and a sequential id
.
This approach scales well to medium and complex apps.
The overall principle here is that the state is read only, and can only be modified by dispatching actions.
Before learning more about Redux, it's important to know the difference between "Pure" and "Impure" functions.
Pure:
function square(x) {
return x * x;
}
function squareAll(items) {
return items.map(square);
}
Pure functions are those whose return values depend only upon the values of their arguments. Pure functions don't have side effects (like network or database calls). They also do not override any existing values. Pure functions are predictable: given the same argument(s) , they will always produce the same return value. In the example above, the function returns a new array, instead of modifying the items
that was passed in.
Impure:
function square(x) {
updateXInDatabase(x);
return x * x;
}
function squareAll(items) {
for (let i = 0; i < items.length; i++) {
items[i] = square(items[i]);
}
}
Contrast this with "impure" functions. Unlike pure functions, impure functions may have side effects, may operate on the DOM, and may override the values you pass to them. In the example above, the function calls a database and overwrites the values that have been passed in.
The distinction between pure and impure functions is important to understand, since Redux will sometimes require you to write pure functions.
React pioneered the idea that the UI layer is most predictable when it is described as a pure function of the application's state.
Redux complements this approach with another idea: that state mutations in your app must be described by a pure function that takes the previous state and the action being dispatched, and returns the next state of your application.
Inside any Redux application, there is one particular function that takes the previous state and the action being dispatched, and returns the next state of the whole application. This function must be pure (i.e. it cannot modify the state given to it) because it has to return a new object representing the application's new state.
Even in large applications, there is still just a single function that calculates the new state of the application. It does so based on the previous state of the whole application and the action being dispatched.
However, this function isn't necessarily slow. If certain parts of the state haven't changed, their references can stay as is. This is what makes Redux fast.
In the ToDo app example, when changing the visibility between "All/Completed/Active" the actual items themselves haven't changed, so the reference to the previous version of the todos
array is left intact.
This is the 3rd and final principle of Redux: to describe state mutations, you must write a function that takes the previous state of the app and the action being dispatched, then returns the next state of the app. This function is called the Reducer.