This part of the article will be about understanding the ingredients of Redux. Are they so necessary, what is their analogue. A more convenient alternative to the useReducer hook will also be proposed.
Actually, I will not reproduce Redux. Instead, the next part will describe how to design a better architecture using MobX. The next part is mainly for those whose projects on MobX turned out to be confusing, with uncontrolled changes.
In this part of the article, I want to show that:
reducers are analogs of ordinary pure functions for obtaining a new state.
selectors are analogous to regular memoized functions that return data.
dispatch + action + action creators are analogous to regular function calls, and the division into dispatch, action, action creators is often unnecessary and used out of place.
The article will not cover the Redux Toolkit and other libraries for reducing the boilerplate. Just the way Redux was originally used. Note that a similar structure of the store code, which the developers of the Redux library came to, existed before the appearance of the Redux Toolkit in a more user-friendly form in other state managers, like MobX, Vuex (I will sometimes mention it, because it is similar to MobX , and I am a little familiar with him).
Contents of the first part
One storage (store) vs multiple storages
Reducer vs pure state mutation function. SOLID violation
Selector vs memoized function that returns data
Business logic in MobX stores vs business logic in middleware's
Action creators, actions, dispatch vs direct function calls
Supplement - do you need useReducer?
One storage (store) vs multiple storages
The single-sided approach has both advantages and disadvantages. But Vuex and MobX projects work great with multiple stores. Even more, the presence of stores in the project is optional. But separating data and logic for working with them from the rest of the program, as well as separating data from logic, provide additional opportunities and convenience during development.
" " , . , ( , localStorage/sessionStorage, ) , . Redux , , .
Reducer vs . SOLID
- , , Redux, . , , , () - .
- ugly switch O(n), actions . O(n) , 60 . โ . swith - [actionNameKey][function] .
- , , , , .
3 SOLID GRASP
, . , / / . . . " SOLID" " ". : " , SOLID, , , ". . - , /. , , , .
" ". , , . .
single-responsibility principle (SRP)
, - . . actions. actions. , . , combineReducers.
/
/ , :
: , .
: , , .
, .
action , . . ( JS ), . , action-.
, . , , Redux - actions , , , . , actions .
LSP - , , , .
https://medium.com/webbdev/solid-4ffc018077da - , : " , , ."
, . , LSP, . , . " ..." , .
. , , , switch, , action -.
.
(High Cohesion) GRASP
, switch, . , . . action.type. , case . / /. - , .
SRP SOLID. . , actions, . , action, .
. . , .
. , โ . , - action, . - . - , , . :
case 'todos/todoAdded': {
return {
...state,
todos: [
...state.todos,
action.paylod.newTodo
]
}
}
:
function todoAdded(state, newTodo) {
return {
...state,
todos: [
...state.todos,
newTodo
]
}
}
- , . type action, Redux, . - . , , / : todoStore['todoAdded'].
vs ,
Redux - . , - . .
MobX (computed values). , , JS . . , Vuex - .
Redux, , middleware. . MobX .
- MobX vs - middleware's
Redux - , , .
Vuex MobX , action API -. Vuex - . , . -, ( ). MVC . MVC wikipedia - MVC, . , Vuex MobX - MVVM, MVC.
, , . , . , , AngularJS 1.x. , .
- . 2 (, api ), . โ , . , .
, - , , , . () .
Action creators, actions, dispatch VS
Redux - . .
, , - . Redux pub/sub (-).
Pub/sub ( ) , .
, , :
. , ( ) .
, - , . , . - React . , .. react-.
Redux action-? :
, middleware;
middleware, ;
, , .
? . , - , . actions - . 3- actions . 3 actions ( ).
1. middleware- ( ). , ?
Action, middleware, . .
. dispatch middleware-? -, . , .. . , middleware- middleware .
-, .
2. Middleware . . action ? . , .
3. actions . actions action , . .
- useReducer?
, - , .
useReducer , . . actions, .. , . useReducer - .
Functionality similar to useReducer can be done manually via useState, but this is long and inconvenient. But you can not do it every time, but take it out separately, which I did. I wrote a useStateWithUpdaters hook to write more readable and usable code. Below is an example of its use:
const updaters = {
subtract: (prevState, value) => (
{ ...prevState, count: prevState.count - value }
),
add: (prevState, value) => (
{ ...prevState, count: prevState.count + value }
),
};
const MyComponent = () => {
const [{ count }, {add, subtract}] =
useStateWithUpdaters({ count: 0 }, updaters);
return (
<div>
Count: {count}
<button onClick={() => subtract(1)}>-</button>
<button onClick={() => add(1)}>+</button>
</div>
);
};
You can find its implementation in issue .
There is a TypeScript version .
You can also improve it a little - combine the new state with the previous one in the implementation of the hook itself, so that you don't have to constantly write "... prevState".