Preface
I have been a React
developer for a long time, and I have developed many large and small applications. Except for React
using it when I was learning at the beginning Redux
, I basically stopped touching it later. No matter how complex the application is, I simply feel that it is better to use it. Context
It can solve all my problems. I am ashamed to say that I have basically never thought about Redux
the reason for its existence. Maybe it is React
really done too well, or maybe our current equipment performance is seriously excessive, so I don’t need to consider application optimization at all.
The question of why so many people use it came up again today Redux
, so I took another look at the documentation of React
and Redux
, and found out quite a lot (every time I read the documentation I gained something new, I recommend you to read it more if you have nothing to do), and suddenly it reminded me of There have been many times useState
where I felt awkward when updating an array (although there was nothing wrong with it, but I always felt it was too complicated). Let’s talk about this today.
Feel free to recommend using the examples/reducer-context-redux I wrote for better results. All the complete codes below can be found in the examples.
need
Talking about some technical things divorced from real needs always makes people feel empty, so today we will talk about a relatively simple requirement and compare the differences in code evolved in several different ways to help us understand Redcuer
, Context
and Redux
these concepts.
Briefly describe the requirements, an todo
input box can be created, a displayed todo
list, todo
the name itself can be modified, marked as completed or deleted, see the figure below for details.
General implementation
You can also see from the picture that this is a very simple requirement. Let us quickly implement it. For the complete code, see examples/reducer-conetxt-redux/base
... // Define a Todos const [todos, setTodos] = useState ([]); // Define several methods to create, update, and delete Todos const handleAddTodo = ( name ) => { setTodos ([...todos, { id : nextId++, name, done : false }]); }; const handleChangeTodo = ( todo ) => { setTodos ( todos. map ( ( t ) => { if (t. id === todo. id ) { return todo; } else { return t; } }) ); }; const handleDelTodo = ( id ) => { setTodos (todos. filter ( ( t ) => t. id !== id)); };
Why did I only post part of the code? Because this part of the code will undergo the first step of evolution, I believe most people will write it this way (if not, don’t blame me, at least this is how I write it in most cases).
But what’s wrong with it? In fact, there is no problem. If you haven’t encountered any problems yet, there is indeed no problem. If you feel like you are talking nonsense, let me give you a few situations where you may encounter problems:
- If Todo needs to be created, updated and deleted through the interface, then when you perform multiple operations at the same time,
todos
only the last update will be completed. - There
1
is a very uncomfortable part, that is, it is not easy to find outtodos
why it is not updated correctly to what you expect. This troubleshooting is very painful. I wonder if you have encountered it? useState
Although the update mechanism seems to be displayed very intuitively nowtodos
, if I add more functions, such as multi-statetodo
, you will need moresetTodos
updates at this timetodos
, which seems to be difficult to understand, then we can更新
change this operation to上一步
Or下一步
, do you need to unpackhandleChangeTodo
this method?
Okay, that’s almost the first version, so how to optimize it?
Reducer
To use it, you have to know what it is first, right? To put it simply, it is to merge all the status update logic into one function, which is called Reducer
. Its definition has come out. I think at this time you may have thought of how Reducer
to update the first version above. For the complete code, see examples/reducer-context-redux/reducer .
function todoReducer ( todos, action ) { switch (action. type ) { case 'added' : { return [...todos, { id : nextId++, name : action. name , done : false }]; } case 'changed' : return todos. map ( ( t ) => { if (t. id === action. todo . id ) { return action. todo ; } else { return t; } }); case 'deleted' : { return todos. filter ( ( t ) => t. id !== action. id ); } default : { throw Error ( 'Unknown action: ' + action. type ); } } } const [todos, dispatch] = useReducer (todoReducer, []); const handleAddTodo = ( name ) => { dispatch ({ type : 'added' , name, }); }; const handleChangeTodo = ( todo ) => { dispatch ({ type : 'changed' , todo, }); }; const handleDelTodo = ( id ) => { dispatch ({ type : 'deleted' , ID, }); };
Looking at it directly, it seems that there is more code than above? This example is indeed true, but just like what I said in the first version 问题3
, when your status becomes more and more complex, the code growth brought about by the two methods will not be the same. In other words, when the number of statuses reaches a certain level, , the code written this way will be less, but this does not seem to be a sufficient reason to write this way.
Then let me add some benefits of writing like this:
- All status changes are contained in the function, and you can easily perceive the status changes
todoReducer
in this function .console.log
-> Convenient for debugging todoReducer
As a clean function, you can easily write its test cases. -> Conveniently write test cases and enhance stability- Status changes are clear at a glance. -> Enhance readability
Of course, these are not necessary. You can use them according to your preferences and scenarios , useState
but useReducer
you should know the difference.
Context
The above code does Context
n’t seem to have any direct connection with it, so why should I take a look at it too? Because Redux
it is like a combination of Reducer
and Context
, so you now know Reducer
what it is, and of course you also need to know Context
what it is. As mentioned above, see examples/reducer-conetxt-redux/context for
the complete code .
So Context
what is it? To put it simply, it means that two components that are not directly connected share state. For example A -> B -> C -> D
, the status D
you want to receive needs to go through the sum , so if you use it , you can skip the sum . Doesn’t it seem very useful? Indeed, it is, but it has a big shortcoming, which is why we don’t want it to be abused, because you need to define this state, then if this state changes, all sub-components will be updated, then if you are in a If a very large application has a frequently changing state defined at its root, then the application must be updated frequently, which is a scary thing.A
B
C
Context
B
C
A
A
The main purpose of looking at it again Context
is to share state across components. It does not have the function of state definition and management, that is, it needs to be matched useState
or useReducer
used. This is why I said Redux
it is like a combination of Reducer
and Context
. Let’s take a look at the code evolution. .
// TodoContext.jsx import { createContext } from 'react' ; export function todoReducer ( todos, action ) { switch (action. type ) { case 'added' : { return [...todos, { id : nextId++, name : action. name , done : false }]; } case 'changed' : return todos. map ( ( t ) => { if (t. id === action. todo . id ) { return action. todo ; } else { return t; } }); case 'deleted' : { return todos. filter ( ( t ) => t. id !== action. id ); } default : { throw Error ( 'Unknown action: ' + action. type ); } } } export default createContext (); // import TodoContext , { todoReducer } from './context' ; const [todos, dispatch] = useReducer (todoReducer, []); return ( < TodoContext.Provider value = {{ todos , dispatch }}> < ContextApp /> </ TodoContext.Provider > );
Of course, you can still useState
use it to replace it useReducer
, it depends on how you think about it!
Redux
Finally we have reached the last step. In fact, if you don’t use it Redux
, it doesn’t seem to prevent you from doing this requirement or any other requirement. But why should you use it? This is what we will explore today. For the complete code, see examples/reducer- conetxt-redux/redux
.
Let’s look at the code transformation first. Due to the new Redux
use Toolkit
to organize the code, but for the convenience of understanding, I will simply use it redux
to demonstrate this transformation.
// store.jsx import { createStore } from 'redux' ; function todoReducer ( todos = [], action ) { switch (action. type ) { case 'added' : { return [...todos, { id : nextId++, name : action. name , done : false }]; } case 'changed' : return todos. map ( ( t ) => { if (t. id === action. todo . id ) { return action. todo ; } else { return t; } }); case 'deleted' : { return todos. filter ( ( t ) => t. id !== action. id ); } default : return todos; } } export default createStore (todoReducer); // import { Provider } from 'react-redux' ; export default () => ( < Provider store = {store} > < ReduxApp /> </ Provider > );
In fact, Context
looking at the compared versions, you can understand at a glance that what I said Redux
is like a combination of Reducer
and , so why use it? Context
Let’s get straight to the reasons:
- I wonder
Context
if you still remember the shortcomings? Changes in state will cause changes in all components, butRedux
will only affect components that subscribe to the corresponding state. - I don’t know if you still remember that there was still an unresolved problem in the original version, which was the status update after asynchronous requests.
Redux
There are many useful middlewares to handle these things. For exampleredux-thunk
, of course you can also write it yourself, but what’s the point? ? - Browser plug-in
Redux DevTools
allows you to clearly see changes in each status. - It is separated from different
UI
state management. For example, if you have multiple applications sharing a set of states at the same time, or two applications youReact
writeVue
share a set of states.
This is Redux
the most powerful advantage I understand, that is, when you don’t have these problems, you don’t need it at all.
Summarize
Like I said, Redux
you may not need it when you haven’t realized whether you want to use it or not. When you are thinking about how to organize your status, or you are already overwhelmed by your status, you may need it. Think about it, it’s nonsense to talk about technology regardless of requirements. You have enough time to continuously optimize your code instead of integrating all the tools into one application from the beginning, regardless of whether it is real or not. Required, so you’ll never understand the point of using it.