preface
After joining the rich road, I was asked to use React to maintain and develop new projects. This was a bit of a learning curve for me as a Vue user. I also used Redux in react development projects. At first I thought it was similar to Vuex, but it wasn’t. After studying for some time, I will summarize my understanding of Redux through this article.
I. The motivation for the creation of Redux
To explain ReDUx, we need to introduce three variables from the front-end design model: View, Modal, and State.
Start with View and Modal:
- View: A View interface that represents the logic of an HTML page.
- Model: Data Model, representing the data needed to interact with the user.
Usually their relationship in the design model looks something like this:
View and Modal are coupled:
View-->Modal
: the user and the<input/>
To change the data in the Model.- Modal–>View: After the network request gets the response, the data in the Model layer changes, causing the View content to change accordingly.
Today’s front-end projects are written with a componentized approach, and each component is usually developed using the above design model when written.
In a front-end project, there is some common data that needs to be used in multiple components, such as UI themes, user information (user names, user roles). These public data are called states. Its connection with Modal and View in the design model is as follows:
State and View Modal are coupled together:
View<-->State
: Set by the user through interactionState
In the site UI theme, changes that in turn affect multipleView
Modal<-->State
: Get user information by request and then update itState
User information, multipleModal
The data needs to be followedState
Update the user information in.
It can be concluded from the above point of View that the change of State will affect multiple Modal and View, so the management of State needs certain requirements. This point can be reinforced by redux’s quote:
Managing changing states is difficult. If a change in one model causes a change in another model, then a change in the View may cause a change in the corresponding model as well as in the other model, which in turn may cause a change in the other view. Until you can’t figure out what’s going on. When, for what reason, and how state changes are out of control. When systems become complex, it can be difficult to reproduce problems or add new features.
There are many plug-ins for State management on the front end, with Flux and Redux being the most widely used. Let’s talk about redux.
Use of Redux in native JS
It’s important to note that there is no relationship between Redux and React. Redux supports React, Angular, Ember, jQuery, and even pure JavaScript.
Here we use native JS and Redux to implement state management, mainly because redux is usually used with React, but we will miss the opportunity to learn some native apis.
1. Explanation of the Redux workflow
The first thing to know about redux’s workflow is this:
1. Useaction
Describes the operation to be performed
Action is a variable used to describe the type of action to store. The format is generally as follows:
{
type: 'ADD'.// There can be other attributes, but type must exist
}
Copy the code
Actions are essentially JavaScript ordinary objects. We agree that an action must use a type field of type string to indicate the action to be performed.
Actions are usually passed to the Store via Dispatch (action), which then passes the action into the Reducer along with the state (that is, the public state to change) for processing.
2. Usereducer
To deal withaction
Reducer is a pure function that receives old states and actions, reads action.type to clarify the processing type, and returns the new state as a new state record to the store.
const reducer = (state=0,action) = >{
switch (action.type) {
case 'ADD':
return state+1
default:
return state
}
}
Copy the code
Use 3.subscribe
To subscribe tolistener
Add a change listener via subscribe(listener). Whenever dispatch(action) is executed, part of the state may have changed. You can call getState() in the callback function to get the current state.
2. Example: Add and subtract numbers
The page in the example has three tag elements, from left to right: the + button, the tag that displays the number, and the – button. Press the + and – buttons to increment and decrement the numbers in the TAB, respectively. As follows:
Github project address
The directory structure is as follows:
store/action/number.js
Define the actions that need to be dispatched
// represents the increment action
export const ADD_NUMBER={
type:'ADD',}// represents the action minus 1
export const SUB_NUMBER={
type:'SUB'
}
Copy the code
store/reducer/number.js
Define the Reducer in the store
// Handle the reducer of actions
const reducer = (state=0,action) = >{
switch (action.type) {
case 'ADD':
return state+1
case 'SUB':
return state-1
default:
return state
}
}
export default reducer
Copy the code
store/index.js
Create the store
import { createStore } from 'redux'
import reducer from './reducer/number'
// Create a store by createStore (reducer).
const store = createStore(reducer,0)
export default store
Copy the code
index.html
Page code
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="Width = device - width, initial - scale = 1.0">
<title>Document</title>
</head>
<body>
<div>
<button id="add">+</button>
<span id="number"></span>
<button id="sub">-</button>
</div>
<script src="./index.js"></script>
</body>
</html>
Copy the code
index.js
import store from './store'
import {ADD_NUMBER,SUB_NUMBER} from './store/action/number'
// Update the span TAB content that displays numbers
function changeNumber(){
document.querySelector('#number').textContent=store.getState()
}
// + the callback function executed after the button is clicked, internally dispatching the + 1 action via dispatch
function add(){
store.dispatch(ADD_NUMBER)
}
// - The callback function executed after the button is clicked, internally dispatches the action minus one via dispatch
function sub(){
store.dispatch(SUB_NUMBER)
}
changeNumber()
// Subscribe registers the listener to execute the incoming callback function when state changes
store.subscribe(() = >{
changeNumber()
})
document.querySelector('#add').addEventListener('click',add)
document.querySelector('#sub').addEventListener('click',sub)
Copy the code
By reading the examples above and explaining the Redux workflow, you can get a clearer picture of how Redux works.
3. Three principles written by Redux
-
Single data source: The entire application can have only one state in which all common state must be stored, and the state can only be stored in one store.
-
State is read-only: the value of State cannot be changed by directly changing State statements like store.getState().args = 1. The only way to change state is to trigger an action.
In addition, since State is read-only and cannot be changed, reducer must return a new State after action. Type is matched in the switch statement block, that is:
// Assume state is the object // Incorrect usage return Object.assign(state, {a:1.b:2}) // Correct usage return{... state,a:1.b:2} // or return Object.assign({}, state, {a:1.b:2}) Copy the code
Return the state passed in if action.type does not match the case in the switch statement, that is, default.
-
Use Reducer as a pure function to perform the modification: We need to note that Reducer function must be a pure function when writing:
To put it simply, a function whose returns depend only on its arguments and have no side effects during execution is called a pure function.
Never do these things in a Reducer:
- Modify the passed parameter;
- Perform operations that have side effects, such as API requests and route jumps;
- Call impure functions, such as date.now () or math.random ().
Example: adding and subtracting numbers and units
Here is an example of adding a requirement based on the number at the beginning of the addition and subtraction. We want to implement not only addition and subtraction, but also the unit switch, as follows:
Github project address
The directory structure remains the same, but there are a few more files
Unit. js files are created in store/ Action and store/reducer to store and reducer actions related to unit changes respectively. The new store/reducer/index.js is used to combine the reducer in number.js and unit.js.
src/action/unit.js
// The user can call this method to get an actio, passing in unit ('cm','mm','m')
export const CHANGE_UNIT=(unit) = >({
type:'CHANGE_UNIT',
unit
})
Copy the code
A function like CHANGE_UNIT that generates action is called Action Creator. Don’t confuse action with Action Creator. Action is a payload of information, and Action Creator is a factory for creating actions (both synchronous and asynchronous).
src/reducer/unit.js
const reducer = (state='m',action) = >{
switch (action.type) {
case 'CHANGE_UNIT':
return action.unit
default:
return state
}
}
export default reducer
Copy the code
src/reducer/index.js
import { combineReducers } from 'redux'
import number from './number'
import unit from './unit'
// Merge reducer through combineReducers. Then no matter how many types of Reducer there are, reducer can be merged into a Reducer through this function and passed into createStore
export default combineReducers({
number,
unit
})
Copy the code
The combineReducers helper function merges an object consisting of several different Reducer functions as values into a final Reducer function, and then calls the createStore method on this Reducer.
The merged Reducer can call each reducer and merge the results they return into a state object. The state object returned by combineReducers() will name each state returned by reducer according to the corresponding key when it is passed to combineReducers().
Finally, there are corresponding changes in store/index.js, index.js, and index.html as shown below:
store/index.js
import { createStore } from 'redux'
import reducer from './reducer'
// Introduce the reducer generated by combineReducers, and make the initial value of state into an object in the second parameter of createStore. Use the name of the sub-reducer as the key name to store the initial value.
const store = createStore(reducer,{number:3.unit:'mm'})
export default store
Copy the code
index.js
import store from './store'
import {ADD_NUMBER,SUB_NUMBER} from './store/action/number'
import {CHANGE_UNIT} from './store/action/unit'
function changeNumber(){
const {number,unit}=store.getState()
document.querySelector('#number').textContent=`${number}${unit}`
}
function add(){
store.dispatch(ADD_NUMBER)
}
function sub(){
store.dispatch(SUB_NUMBER)
}
// From './store/action/unit' we introduce a function that generates action involving unit changes, i.e. CHANGE_UNIT.
function changeUnit(unit){
store.dispatch(CHANGE_UNIT(unit))
}
store.subscribe(() = >{
changeNumber()
})
changeNumber()
document.querySelector('#add').addEventListener('click',add)
document.querySelector('#sub').addEventListener('click',sub)
// Bind events to buttons
document.querySelector('#m').addEventListener('click'.() = >{changeUnit('m')})
document.querySelector('#cm').addEventListener('click'.() = >{changeUnit('cm')})
document.querySelector('#mm').addEventListener('click'.() = >{changeUnit('mm')})
Copy the code
index.html
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="Width = device - width, initial - scale = 1.0">
<title>Document</title>
</head>
<body>
<div>
<button id="add">+</button>
<span id="number"></span>
<button id="sub">-</button>
</div>
<! Add the corresponding element button -->
<div>
<button id="m">m</button>
<button id="cm">cm</button>
<button id="mm">mm</button>
</div>
<script src="./index.js"></script>
</body>
</html>
Copy the code
Redux in the React framework
Let’s implement the above example with React. React has nothing to do with Redux. In order to use Redux more flexibly in projects using React as the front-end framework, we used the React – Redux plugin. The application is also as follows:
Github project address
The directory is as follows:
The contents of the Store folder are unchanged, as we are only converting the project from native javascript to React, and the contents of the Store section do not need to be changed. The components folder was added to store the React component. We split the page into two parts, as shown in the following figure:
JSX is responsible for the logic of incrementing and decrement content, while unit.jsx is responsible for the logic of switching units. Both components are eventually referenced by app.jsx, which makes up the content of the page. The code for app.jsx is shown below:
components/App.jsx
import Unit from './Unit'
import Number from './Number'
import React from 'react';
const App = () = >(
<div>
<Number/>
<Unit/>
</div>
)
export default App
Copy the code
Let’s look at the code for the React components:
components/Number.jsx
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import {ADD_NUMBER,SUB_NUMBER} from '.. /store/action/number'
import React from 'react';
// Number Component logic
const Number = ({add,number,unit,sub}) = >{
return (
<div>
<button onClick={add}>+</button>
<span>{number}{unit}</span>
<button onClick={sub}>-</button>
</div>)}Number.propTypes={
// Define number and unit to be injected from store state
number: PropTypes.number.isRequired,
unit: PropTypes.string.isRequired,
// Define add and sub for addition and subtraction, respectively
add: PropTypes.func.isRequired,
sub: PropTypes.func.isRequired,
}
const mapStateToProps = ({number,unit}) = > ({
number,unit
})
const mapDispatchToProps = (dispatch, ownProps) = > ({
// The addition and subtraction methods are actions corresponding to dispatches
add: () = > dispatch(ADD_NUMBER),
sub: () = > dispatch(SUB_NUMBER)
})
// Use connect to combine the State of the store and custom methods for distributing actions into the component, which can be called via props
export default connect(mapStateToProps,mapDispatchToProps)(Number)
Copy the code
components/Unit.jsx
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import {CHANGE_UNIT} from '.. /store/action/unit'
import React from 'react';
// Unit component logic
const Unit = ({changeUnit}) = >{
return (
<div>
<button onClick={()= >changeUnit('m')}>m</button>
<button onClick={()= >changeUnit('cm')}>cm</button>
<button onClick={()= >changeUnit('mm')}>mm</button>
</div>
)
}
Unit.propTypes={
// Define changeUnit for converting units
changeUnit: PropTypes.func.isRequired,
}
const mapDispatchToProps = (dispatch) = > ({
changeUnit:(unit) = >dispatch(CHANGE_UNIT(unit))
})
export default connect(null,mapDispatchToProps)(Unit)
Copy the code
JSX components/ number. JSX and components/ unit. JSX components/ number. JSX
- Write the functional components, clear the logic and know which variables need to be imported from outside, best use
prop-types
Write toprops
Data verification - If you need to use
store
In thestate
Data to be definedmapStateToProps
. Let the component change if necessarystate
Inside the data capabilities to be definedmapDispatchToProps
. - In the end,
connect
Methods themapStateToProps
andmapDispatchToProps
Inject into the functionality of step one
Here we talk about the connect method:
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
A method for associating the React component with Redux. Connect is a Kerrized function that obtains the react component by passing two parameters. The first parameter is passed in variables associated with store, such as mapStateToProps and mapDispatchToProps. The second parameter is passed in the pure React component.
Connect does not change the original component class. Instead, it returns a new component class that is connected to the Redux Store.
Let’s start with the argument passed in the first time:
-
mapStateToProps:
Structure: (state, [ownProps])=>stateProps
Description:
If you define this parameter, the component listens for store changes. Any time the Store changes, the mapStateToProps function is called. The callback function must return a pure object, which is merged with the component’s props. Thus, there are the following processes:
A change in state in graph TD Store -- mapStateToProps is called -- a combination of pure objects and component props -- a change in component props triggers the component render function to execute -- a component UI update
Through the above process, relevant state changes in store are realized to trigger component updates.
If you omit the mapStateToProps parameter (e.g. Components/ununit.jsx), your component will not listen to store. If the second argument in the callback function, ownProps, is specified, the value of this parameter is props passed to the component, and mapStateToProps is called whenever the component props is updated (for example: If the parameters passed by the parent component to the child component change, causing the props of the child component to change, mapStateToProps will be called again.
-
mapDispatchToProps
This parameter has two types of structures, objects and functions:
1. Object: If an Object is passed, then each function defined in that Object will be treated as action Creator, and the method name defined by the Object will be the property name; Each method returns a new function in which the Dispatch method takes the return value of Action Creator as an argument. These properties are incorporated into the props of the component.
2. Function:
(dispatch, [ownProps])=>dispatchProps
If you pass in a function, the function will receive a dispatch function to dispatch the action, and you will then decide how to return an object that is somehow bound to the Action Creator via the Dispatch function. If you omit the mapDispatchToProps parameter, by default dispatch is injected into your component props. If the second argument in the callback function, ownProps, is the props passed to the component, and mapDispatchToProps is called whenever the component receives the new props.
All right, components, that’s the functionality of the page. Let’s go ahead and look at index.html and index.jsx:
index.html
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="Width = device - width, initial - scale = 1.0">
<title>Document</title>
</head>
<body>
<! -- replace the content of the original page with a div#app-->
<div id="app"></div>
<! -- <div> <button id="add">+</button> <span id="number"></span> <button id="sub">-</button> </div> <div> <button id="m">m</button> <button id="cm">cm</button> <button id="mm">mm</button> </div> -->
<! -- Introduced file format from JS to JSX -->
<script src="./index.jsx"></script>
</body>
</html>
Copy the code
index.jsx
import React from 'react';
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import App from './components/App'
import store from './store'
render(
<Provider store={store}>
<App/>
</Provider>.document.getElementById('app'))Copy the code
enables connect() methods at the component level to obtain stores. Normally, your root component should be nested within to use the connect() method.
Summary: In general, React-Redux provides only two apis that we commonly use: Connect and
Afterword.
This article gives you an idea of most of Redux’s apis (I’ll write another article about applyMiddleware), from the motivation behind Redux’s creation to examples of Redux’s use in native JS. The use of Redux in the React framework gives you an idea of the API that react-Redux provides to connect React with Redux.
My next article will focus on the middleware of Redux to introduce asynchronous actions and the use and principles of the Redux-Thunk and Redux-Saga plug-ins that handle them.