Here’s a mind map for this article:
1. Why Redux
When using Redux on a project, I sometimes think I will use it, but don’t understand why. During debugging, the cause cannot be quickly debug. And the Redux source code is not complicated, with only five apis exposed as a good starting point for reading the source code, so it’s fun to explore Redux with everyone here. If there are some inaccuracies, you are welcome to mention them; In particular, I hope that we can have active discussions and come up with more ideas.
Why does Redux exist
To understand Redux, start with Flux. You can think of Redux as an implementation of the Flux idea. So why did Redux come up? So we’re going to talk about MVC.
1, the MVC
Speaking of Flux, we have to mention the MVC framework.
The MVC framework divides applications into three parts:
- View: displays the user interface
- Controller: Manages the behavior and data of the application, responding to user input (often from View) and status update instructions (often from Model)
- Model: Manage the data, and most of the business logic is in the Model
The user request goes to the Controller, and the Controller calls the Model to get the data, which it passes to the View. The idea is an ideal one. In the real world, most frameworks allow the View to communicate directly with the Model. As the project gets bigger and bigger, this dependency between different modules becomes “unpredictable”, so it becomes like this.
Although this diagram may be exaggerated, it also illustrates the problem that MVC can easily cause data confusion in large projects.
So Flux was born. I looked at a lot of sources before writing this article, some of which said that Flux thought replaced the MVC framework, but I don’t think so. Personally, Flux idea more strictly controls the direction of MVC data flow. Let’s take a look at how Flux tightly controls data flow.
2, Flux,
A Flux application consists of four parts:
- Dispatcher handles action distribution and maintains dependencies between stores
- Store, which is responsible for storing data and handling data-related logic
- Action triggers the Dispatcher
- View, the View, is responsible for displaying the user interface
As can be seen from the figure above, Flux is characterized by one-way data flow:
- The user initiates an Action object to the Dispatcher in the View layer
- The Dispatcher receives the Action and asks the Store to update it accordingly
- Store makes the corresponding update and issues a change event
- The View updates the page after receiving the change event
Therefore, under the Flux system, if you want to drive the interface, you can only distribute a Store, there is no other way. Under this rule, it is easy to trace the logic of an application. And this idea solves the problem that MVC can’t eliminate direct dialogue between View and Model.
I will not talk about specific examples of Flux here. If you want to know more about Flux, you can read Ruan Yifeng’s Introduction to Flux Architecture tutorial.
4. Redux was born
Redux is an implementation of Flux, which means that in addition to “one-way data flow”, Redux also emphasizes three basic principles:
- The only store (Single Source of Truth)
- Keep the State read-only (State is read-only)
- Changes to data are made with pure functions only
A. Unique store
In Flux, an application can have multiple stores. However, data redundancy is easily caused by dividing multiple stores. Data consistency is difficult to handle, and stores may depend on each other, which increases the complexity of the application. So Redux’s solution to this problem is to have only one Store for the entire app.
B. Keep the status read-only
You just can’t change the state directly. If you want to change the state, you can only do this by distributing an Action object.
C. Data changes can only be done by pure functions
The pure function here is Reducer. According to Dan, author of Redux: Redux = Reducer + Flux
Use Redux on React
Use Reudx in React.
1. Data flow in Redux
Creating a Redux application requires the following parts:
- Actions
- Reducers
- Store
What do they mean respectively? To put one’s foot in one’s mouth to put one’s foot in one’s mouth
The store manager came to inspect, and found that the shoes 2 were too high, and the shoes were the main promotion style in the store, so it was not suitable for publicity. So he asked the clerk to move the shoes 2 down two rows. After putting them down, the store manager looked more comfortable.
Redux can now be explained with the above example:
- View: The overall effect of the shoes on the shoe rack
- Action: the task assigned by the store manager to the clerk (moves the shoes down)
- Reducers: People who perform specific tasks (move your shoes down two rows)
- Store: The specific position of the shoe on the shoe shelf
So the whole process could look like this:
The Store determines the View, and then the user interaction generates actions. Reducer performs tasks according to the received actions, thus changing the state in the Store and finally displaying it on the View. How did the Reducer receive the Action signal? With this question, let’s look at an example.
2. Redux practice
Now that you know what each part of Redux stands for, let’s take a closer look at how Redux works with a counter example (see GitHub for the code). The end result we want is as follows:
According to the above thinking, Action and Reducer can be defined as:
- Action: plus
- Reducer: Add 1
Let’s create Action and Reducer files:
Actions
First we create actiontypes.js and actions.js files. ActionType represents the type of Action, and you can see that it is a constant. In actions.js, we define two Action constructors that return a plain object, and each object must contain a Type attribute.
Action specifies what we want to do (add and subtract).
Some of you might ask, in an Action, sometimes you return a function, not a simple object. In this case, it is the middleware that intercepts the Action, and if it is a function, it executes the middleware method. But we’re not going to talk about middleware this time, so we’ll ignore that for now.
Reducer
You can see that Reducer is a pure function. It takes two arguments, state and Action, determines what it needs to do with the current state based on the received state and Action, and returns the new state.
In the Reducer we gave state a default value, which is our initial state. Read on to see how Redux returns the initial value.
We have both Action and Reducer, so how do we make them connect? Let’s take a look at the best part of Redux – Store.
createStore
First we create a Store:
In store.js, we passed the Reducer to the createStore method and executed it to create the store. This approach is the essence of Redux.
Take a look at the createStore source section below:
CreateStore accepts three parameters:
reducer{Function}
state{any}
(Optional parameter)enhancer{Function}
(Optional parameter)
Return an object containing five methods. Let’s focus on the first three for now:
- dispatch
- subscribe
- getState
In the entire createStore, only dispatch({type: actiontypes.init}) is executed. So what did Dispatch do?
I’ve omitted some code, which is the core of the Dispatch method. It receives an action object and passes the state parameters received by the createStore and the action parameters passed in through the Dispatch method to the Reducer and executes. Then assign the state returned by the Reducer to currentState. Finally, the method in the subscription queue is executed.
The createStore method starts with dispatch({type: actiontypes.init}). The main purpose of this statement is to initialize state.
Now we have linked actions with Reducer. As you can see, in the createStore method, it maintains a variable currentState and updates the currentState variable through the Dispatch method. To retrieve currentState, we simply call getState as exposed by createStore:
The getState method gets the current currentState variable, and if you want to getState in real time, you need to register for listening events, which will be executed every time you dispatch.
Now let’s comb through the ideas:
Action
: Purpose of the actionReducer
: Perform specific operations according to the Action command receivedStore
: Pass the Action to Reducer and update the state. The method in the subscription queue is then executed.
Redux and React are two separate products. However, if the two products are used together, the react-Redux library will have to be introduced. This library can greatly simplify code writing.
2. Store and context
As you know, in React we all use props to pass data. React is a tree of components, passing data down one layer at a time.
However, in a multi-layer nested component structure, only the innermost component needs to use this data, so that all the components in the middle need to help pass this data, we need to write props many times, which is very troublesome.
React provides a function called Context to solve this problem.
Context is a “context” that allows all components in a tree to access a common object. In order to accomplish this task, the upper and lower components need to cooperate with each other.
First, the parent component declares that it supports the context and provides a function to return an object representing the context.
The child components can then access the common object through this.context by declaring that they need the context.
So we can use the React context and attach the Store to it, and we can share the Store globally.
React: Share Store on React
Provider
Provider, as the name implies, is the Provider, in this case, the Provider of the context.
Use it like this:
Provider provides a function getChildContext, which returns the object that represents the context. We can get this from context when we call Store: this.context.store.
In order for the Provider to declare itself as the Provider of the context, it also needs to specify the Provider’s childContextTypes property (which needs to be matched with getChildContext).
The Provider can access the context only if it has the above two characteristics.
Now that we are done with the Provider component, we can attach the context to the top-level component of the entire application.
The entry file index.js for the entire application:
We pass the Store as props to the Provider component, which hangs the Store on the context. So now we’re going to get the Store from the context.
consumers
Below is the skeleton of our entire counter application.
Let’s render the page first:
In the above component, we do two things:
- The first thing is: say you need the context
- The second thing is: initialize state.
How do you claim that you need the context?
- The first thing you need to do is give the App component
contextTyp
E assigns a value of the same type as the context provided in the Provider. - Then add the context to the constructor so that the rest of the component can pass through
this.context
So let’s call the context. - And then theInitialize the state. If you look at the code, we’re calling the Store that’s hanging on the context
getState
Methods.
As we saw above, the getState method returns the same variable maintained in the createStore method. This variable is initialized when the createStore is executed.
Now let’s add the action to the plus sign.
We want to add the number by one, so we have an “add” Action, and that Action is an Action, and that Action is addAction. If you want to trigger this action, you need to execute the Dispatch method.
The Action object is passed to the Reducer using the Dispatch method. After processing, the Reducer returns a new state plus 1.
In fact, the data in the Store is now up to date, so we can see that the page hasn’t been updated yet. So how do we get the latest state?
To subscribe to
Just like following an official account, I just need to subscribe at the beginning, and then every time I get an update, I get a push.
This is where we use the Store subscribe method. As the name implies, I want to subscribe to the change in state. Let’s take a look at the code:
In the component’s componentDidMount lifecycle, we call the Store SUBSCRIBE method, and every time the state is updated, we call the onChange method; In the onChange method, we get the latest state and assign it. We unsubscribe when the component is uninstalled.
This completes the subscription function. When you run the program again, you will see the latest number displayed on the page.
react-redux
In this example, you can see that we can abstract out a lot of logic, such as the Provider and the ability to subscribe to store changes. In fact, the React-Redux has already done that for us.
- Provider: provides the context containing the store
- Connect: Converts state to props for the inner component to listen for changes in state and optimize component performance
In this example, we simply implement some of the functions of React-Redux. Specific you can go to the official website to see.
conclusion
Redux and React use the following data streams:
Good ~ we have completed the entire application. Now does everyone understand how Redux works?
The code can be found on GitHub.
References:
- Brief introduction to MVC, MVP and MVVM architecture patterns
- Ruan Yifeng – Flux Architecture primer
- React and Redux by Cheng Mo
Permanent links to this article:
- Zhihu column
- Making the address