Redux Introduction Notes
Recently started to read some knowledge about react, and inevitably came into contact with redux.
I took a look at his doc to briefly summarize.
Note that most of the original content is outdated, because react updated the hook. I will go back and study what improvements react-redux has made to the hook, and then reexplain it. However, most of the content of this blog is about native redux, which can also be safely eaten.
What is Redux?
Redux is a JavaScript state container that provides predictable state management. (If you need a WordPress framework, check out Redux Framework。)
It allows you to build consistent applications that run in different environments (client, server, native applications) and are easy to test. Not only that, it also provides a super cool development experience, such as having a时间旅行调试器可以编辑后实时预览。
** Redux except and React In addition to using it together, it also supports other interface libraries **. It is small and powerful (only 2kB, including dependencies).
Redux in brief
All states in the application are stored in a single * store * in the form of an object tree. The only way to change the state is to trigger * action *, an object that describes what happened. To describe how actions change the state tree, you need to write * reducers *.
That’s it!
1 | import { createStore } from 'redux'; |
Instead of modifying the state directly, you should make the modification into a normal object called * action *. Then write a special function to determine how each action changes the state of the application, this function is called * reducer *.
Core concepts
Redux itself is simple.
When using normal objects to describe the state of an application. For example, the state of a todo application might look like this:
1 | { |
This object is like “Model”, except that it has no setter (modifier method). Therefore, other code cannot modify it at will, resulting in a bug that is difficult to reproduce.
To update the data in state, you need to initiate an action. An Action is a normal JavaScript object (notice, there is no magic here) used to describe what happened. Here are some examples of actions:
1 | { type: 'ADD_TODO', text: 'Go to swimming pool' } |
** The benefit of forcing action to describe all changes is that you can clearly know what happened in the application **. If something changes, you can know why. Action is like an indicator describing what happened. Eventually, ** in order to string action and state together, develop some function, this is the reducer **.
Again, without any magic, the reducer is just a function that takes state and action and returns the new state. For large applications, it is unlikely to just write one such function, so we write many small functions to manage parts of the state separately:
1 | function visibilityFilter(state = 'SHOW_ALL', action) { |
Then develop a reducer that calls these two reducers to manage the state of the entire application.
1 | function todoApp(state = {}, action) { |
Three principles
Single data source, i.e. only one state
- state is read-only, you cannot assign a value to state directly, the only way to modify state is to trigger action
- Use pure function to perform modification. When an action is triggered, the reducer needs a pure function, that is, it cannot directly modify the state, but returns a new state object.
Basic
Action
First, let’s define action.
** Action ** is the payload that transfers data from the application (Translator’s Note: The reason why it is not called a view here is that the data may be server responses, user input or other non-view data) to the store. It is the ** only * source of stored data. Generally you will pass store.dispatch()
Pass the action to the store.
The action to add a new todo task is like this:
1 | const ADD_TODO = 'ADD_TODO' |
** Actions are essentially ordinary JavaScript objects **. We agree that a’type ‘field of type string must be used in the action to represent the action to be performed. In most cases,’ type 'will be defined as a string constant. When the application scale is getting larger, it is recommended to use a separate module or file to store actions.
Action
** Action creates function ** is the method of generating action. The concepts of “action” and “action creates function” are easy to mix together, and it is best to pay attention to the distinction when using them.
The action creation function in Redux simply returns an action:
1 | function addTodo(text) { |
Doing so will make action creation functions easier to port and test.
In Redux, simply pass the result of the action creation function to the dispatch () method to initiate a dispatch process.
1 | dispatch(addTodo(text)) |
Or create a ** bound action creation function ** to automatically dispatch:
1 | const boundAddTodo = text => dispatch(addTodo(text)) |
Then call them directly:
1 | boundAddTodo(text); |
Reducer
** Reducers ** Specifies how to respond to changes in the application state actions Sent to the store, remember that actions only describe the fact that something happened, and do not describe how the application updates the state.
Action
Now that we have determined the structure of the state object, we can start developing the reducer. A reducer is a pure function that takes the old state and action and returns the new state.
1 | (previousState, action) => newState |
The reason why such a function is called a reducer is because it is the same as the function being passed in Array.prototype.reduce(reducer, ?initialValue)
The callback functions in are of the same type. It is very important to keep the reducer pure. ** Never ** do these operations in a reducer:
- Modify the incoming parameters;
- Perform operations with side effects, such as API requests and route jumps;
Call a non-pure function, such as Date.now () or Math.random ().
Remember that the reducer must be kept pure. ** As long as the passed parameters are the same, the next state returned from the calculation must be the same. No special cases, no side effects, no API requests, no variable modifications, just perform the calculation. **
Split and Synthesis of Reducer
As the state becomes more and more complex, our reducer will inevitably become more and more complex, so we have to find a way to split the reducer and finally combine it like a way, because we can only have one state.
For the example we started with in the Core Concepts section, Redux provides combineReducers()
Utility class
1 | import { combineReducers } from 'redux' |
Note that the above writing is exactly equivalent to the following:
1 | export default function todoApp(state = {}, action) { |
You can also give them different keys or call different functions. The following two synthetic reducer methods are exactly equivalent:
1 | const reducer = combineReducers({ |
combineReducers()
所做的只是生成一个函数,这个函数来调用你的一系列 reducer,每个 reducer 根据它们的 key 来筛选出 state 中的一部分数据并处理,然后这个生成的函数再将所有 reducer 的结果合并成一个大的对象。
Store
** Store ** is the object that connects them together. The Store has the following responsibilities:
Maintain the state of the application;
- Provide
getState()
Method to obtain state; - Provide
dispatch(action)
Method update state; - Through
subscribe(listener)
Registration of listeners; - Through
subscribe(listener)
The returned function unregisters the listener.
Again ** Redux application only has a single store **. When you need to split data processing logic, you should use reducer 组合 Instead of creating multiple stores.
It is very easy to create a store based on an existing reducer. In前一个章节In, we use combineReducers()
Merge multiple reducers into one. Now we import it and pass createStore()
。
1 | import { createStore } from 'redux' |
createStore()
的第二个参数是可选的, 用于设置 state 初始状态。这对开发同构应用时非常有用,服务器端 redux 应用的 state 结构可以与客户端保持一致, 那么客户端可以将从网络接收到的服务端 state 直接用于本地数据初始化。
1 | let store = createStore(todoApp, window.STATE_FROM_SERVER) |
A few questions
So far, the basic concepts and usage of Redux have been explained, and we still have several problems
So far, it seems that React and Redux do not have any linkage, there is no relationship, at least there is no special care in usage.
- Redux looks like a state management, not global state management, so why can you do global state management in react?
- How to implement an asynchronous Action, what if I just want to perform some side effects in the Reducer after triggering the Action?
Let’s solve it one by one
With React
There is no connection between Redux and React. Redux supports React, Angular, Ember, jQuery and even pure JavaScript.
Redux does not include by default React 绑定库It needs to be installed separately.
1 | npm install --save react-redux |
If you don’t use npm, you can also get the latest UMD package from unpkg (including开发环境包And生产环境包). If you import a UMD package with the < script > tag, it will throw a window. ReactRedux object globally.
Container components (Smart/Container
Redux’s React binding library is based on 容器组件和展示组件相分离 Development ideas. So it is recommended to read this article first and then come back to continue learning. This idea is very important.
This article predates hooks and was the most popular development method in the class era, but when hooks appeared, the author acknowledges that hooks are a better practice, but if your version does not support hooks, this idea is still very important.
So let’s summarize the differences again:
Display Components | Container Components | |
---|---|---|
Function | Describe how to display (skeleton, style) | Describe how to run (data acquisition, status update) |
Direct use of Redux | No | Yes |
Data source | props | Listening to Redux state |
data modification | call callback function from props | dispatch actions to Redux |
Call method | Manual | Usually generated by React Redux |
Most components should be presentational, but generally a few container components are needed to connect them to the Redux store. This and the following design brief do not mean that container components must be at the top of the component tree. If a container component becomes too complex (for example, it has a lot of nested components and passes countless callback functions), then introduce another container in the component tree, likeFAQAs mentioned in
Technically, you can use’store.subscribe () 'to write container components directly. But the reason why this is not recommended is that you cannot use the Performance optimization brought by React Redux. Therefore, instead of writing container components by hand, use the’connect () ’ method of React Redux to generate **, which will be described in detail later.
Why all components can access the state of redux
All container components have access to the Redux store, so you can listen to it manually. One way is to pass it as props to all container components. But this is too much trouble because you have to wrap the display component with’store ', just because a container component happens to be rendered in the component tree.
The recommended way is to use the specified React Redux component `` Come 魔法般的 Make the store accessible to all container components without having to explicitly pass it. Just use it when rendering the root component.
index.js
1 | import React from 'react' |
Middleware creates asynchronous actions, reducers with side effects
Standard practice is to use Redux Thunk 中间件To use it, we need to introduce the specialized library of’redux-thunk '. We 后面 It will introduce how middleware works in general; for now, you only need to know one important point: by using the specified middleware, the action creating function can return a function in addition to an action object. In this case, the action creating function becomes thunk。
When an action creates a function and returns a function, the function is executed by Redux Thunk middleware. This function does not need to be pure; it can also have side effects, including executing asynchronous API requests. This function can also dispatch actions, just like dispatch the synchronous action defined earlier.
We can still define these special thunk action creation functions in actions.js.
actions.js
1 | import fetch from 'cross-fetch' |
How did we introduce Redux Thunk middleware into the dispatch mechanism? We used applyMiddleware()
, as follows:
index.js
1 | import thunkMiddleware from 'redux-thunk' |
One advantage of thunk is that its results can be dispatched again:
actions.js
1 | import fetch from 'cross-fetch' |
This allows us to gradually develop complex asynchronous control flows while keeping the code as clean as ever.
index.js
1 | store |