Link: Document address.
1. Get to know Redux
1.1 JavaScript pure functions
In functional programming, there is a concept called pure function. JavaScript conforms to the paradigm of functional programming, so there is also the concept of pure function;
In React, the concept of pure function is very important, and it is also very important in Redux, so it is necessary for us to review pure function.
Wikipedia definition of pure functions:
In programming, a function is called a pure function if it meets the following conditions;
- This function needs to produce the same output when it has the same input value. The output and input values of the function have nothing to do with other hidden information or states, and have nothing to do with the external output generated by the I/O device.
- This function cannot have semantically observable function side effects, such as triggering events, causing the output device to output, or changing the contents of objects other than the output value.
Of course, the above definition will be too obscure to understand, so I'll briefly summarize it;
- A certain input must have a certain output;
- The function cannot produce side effects during execution;
Then let's see if some functions are pure functions;
Case 1:
function sum(num1,num2){ return num1+num2 }
- Obviously, sum is a pure function;
- Its output depends on the content we input, and there are no side effects in the middle;
Case 2:
let foo=5 function add(num){ return foo+num } console.log(add(1))//6 foo=6 console.log(add(1))//7
- The add function is not a homomorphic function;
- The function depends on external variables. When the variables change, they will affect the determined input and produce the determined output;
- Can it be changed to a pure function? const foo=2;
Case 3:
function printInfo(info){ console.log(info.name,info.age) info.name="Demacia" }
- printInfo is not a pure function;
- Although no matter what is input, the final output result is undefined, it has side effects and modifies the incoming object;
Of course, there are many variants of pure functions, but we only need to understand its core.
Why are pure functions so important in functional programming?
- Because you can write and use at ease
- When you write, you ensure the purity of the function, just implement your own business logic, and you don't need to care about the incoming content or rely on other external variables;
- When you use it, you make sure that your input content will not be tampered with arbitrarily, and the input you determine will have a certain output;
In React, we are required to protect their props from being modified, whether it is a function or a class component, just like a pure function;
In the later learning of redux, reducer is also required to be a pure function.
1.2 why do you need redux?
Applications developed by JavaScript have become more and more complex;
- JavaScript needs to manage more and more complex states;
- These statuses include the data returned by the server, cached data, data generated by user operations, etc., as well as some UI statuses, such as whether some elements are selected, whether loading effects are displayed, and current paging;
Managing changing state s is very difficult;
- There will be interdependence between states. The change of one state will cause the change of another state, and the View interface may also cause the change of state;
- When the current application is complex, when, why and how the state has changed will become very difficult to control and track;
React helps us solve the DOM rendering process in the view layer, but the State is still left to us to manage;
- Whether the state defined by the component itself or the communication between components is transmitted through props; It also includes data sharing through Context;
- React is mainly responsible for helping us manage the view. How to maintain the state is ultimately up to us;
UI = render(state) is the idea of react
Redux is a container that helps us manage states. Redux is a JavaScript State container that provides predictable State management;
In addition to using with React, Redux can also be used with other interface libraries (such as Vue), and it is very small (including dependencies, only 2kb);
1.3 core concepts of Redux
The core concept of Redux is very simple;
For example, we have a list of friends to manage;
- If we do not define a unified specification to operate this data, the change of the whole data cannot be tracked;
- For example, somewhere on the page through products The push method adds a piece of data;
- For example, the same interface is through products [0] Age = 25 modifies a piece of data;
- The whole application is complex. When there is a bug, it is difficult to track where the change happened;
const initState={ friends:[ {name:'Zhang San',age:18}, {name:'Li Si',age:20}, {name:'Wang Wu',age:24} ] }
Redux requires us to update data through action;
- All data changes must be updated through dispatch(action);
- action is a common JavaScript object used to describe the type and content of this update;
For example, here are some action s to update friends
- The advantage of mandatory use of action is that you can clearly know what changes have taken place in the data, and all data changes are traceable and predictable;
- Of course, at present, our action is a fixed object. In real applications, we will define it through functions and return an action object;
const action1={type:"ADD_FRIEND",info:{name:"lucy",age:18}} const action2={type:"INC_AGE",index:0} const action3={type:"CHANGE_NAME",playload:{index:0,newName:"Xiao Ming"}}
But how do you associate state with action? The answer is reducer
- reducer is a pure function;
- What reducer does is to combine the incoming state and action to generate a new state;
function reducer (state=initState,action){ switch(action.type){ case "ADD_FRIEND": return {...state,friends:[...state.friends,actioin.info]} case "INC_AGE": let arr=state.friends.map((item,index)=>{ if(index===action.index){ return {...item,age:item.age+1} } return item }) return {...state,friends:arr} case "CHANGE_NAME": let arr=state.friends.map((item,index)=>{ if(index===action.index){ return {...item,name:action.newName} } return item }) return {...state,friends:arr} default: return state; } }
1.4 three principles of Redux
Single data source
The state in the whole application is stored in an object tree, and the object tree is only stored in one store;
- Redux does not force us to create multiple stores, but that is not conducive to data maintenance;
- A single data source can make the state of the whole application easy to maintain, track and modify;
state is read-only
The only way to modify must be to trigger action. Don't try to modify state in any way anywhere else;
- This ensures that neither the View nor the network request can directly modify the state. They can only describe how they want to modify the state through action;
- In this way, it can ensure that all modifications are processed centrally and executed in strict order, so there is no need to worry about race condition;
Use pure functions to perform modifications
Connect the old State and action through reducer, and return a new State;
- As the complexity of the application increases, we can split the reducer into several small reducers to operate part of different state tree s;
- However, all reducer s should be pure functions without any side effects;
2. Basic use of Redux
2.1 Redux usage process
Install Redux
npm install redux --save # or yarn add redux
Here, I will simply learn about Redux by creating a simple js file;
Build project structure
1. Create a new project folder: Redux learn
# perform an initialization operation yarn init # Install redux yarn add redux
2. Create src directory and index JS file
- There is no content for the time being
3. Modify package JSON, you can execute index js
"scripts": { "start": "node src/index.js" }
Start at index JS
1. Create an object as the state we want to save
const redux=reqire("redux") const initState={ counter:0 }
2. Create a Store to Store this state
- When creating a store, you must create a reducer
- We can use store Getstate() to get the current state
//Create reducer function reducer(state=initState,action){ return state } //Create a store based on reducer const store=redux.createStore(reducer) console.log(store.getState())//{counter:0}
3. Modify state through action
- Dispatch action s through dispatch
- Usually, there is a type attribute in the action, and other data can also be carried
store.dispatch({ type: "INCREMENT" }) store.dispath({ type: "DECREMENT" }) store.dispatch({ type: "ADD_NUMBER", number: 5 })
4. Modify the processing code in reducer
- Here, we must remember that reducer is a pure function, and there is no need to modify the state directly;
- Later, we will talk about the problems caused by directly modifying the state
const reducer = (state = initialState, action) => { switch (action.type) { case "INCREMENT": return {...state, counter: state.counter + 1}; case "DECREMENT": return {...state, counter: state.counter - 1}; case "ADD_NUMBER": return {...state, counter: state.counter + action.number} default: return state; } }
5. Monitor the changes of the store before distributing the action;
store.subscribe(() => { console.log(store.getState()); })
The complete case code is as follows
const redux = require("redux"); function add() { return { type: "add", speed: 2, }; } function reduce() { return { type: "reduce", speed: 1, }; } const initState = { counter: 9, }; function reducer(state = initState, action) { switch (action.type) { case 'add': return {...state,counter:state.counter+action.speed} case 'reduce': return {...state,counter:state.counter-action.speed} default: return state } } let store = redux.createStore(reducer); store.subscribe(()=>{ console.log(store.getState()); }) setTimeout(() => { store.dispatch(add(2)) }, 2000); setTimeout(() => { store.dispatch(reduce(1)) }, 6500);
2.2 Redux structure division
If we write all the logic code together, it will be difficult to maintain when Redux becomes complex.
Next, I will split the code, and split the store, reducer, action and constants into files one by one;
Note: ES6 support in node
At present, the node version I use is 12.18.3, which is from node V13 Since 2.0, node supports modularization;
- node v13. The following operations are required before 2.0
- In package JSON add attribute: "type":"module";
- Add the following options in the command: node -- experimental modules Src / index js;
- node v13. The following operations are required before 2.0
- In package JSON: add the attribute "type":"module"
Note: when importing files, you need to keep up with * * js * * suffix
Split Redux
1. Create store / index JS file
import redux from 'redux' import reducer from './reducer.js' let store =redux.createStore(reducer) export default store
2. Create a store / reducer JS file
- 2.1 non splitting
import {ADD,REDUCE,PUSHARR,POPARR}from './constants.js' const initState={ counter:1, arr:[111,222,333] } function reducer(state=initState,action){ switch (action.type) { case ADD: return {...state,counter:state.counter+action.speed} case REDUCE: return {...state,counter:state.counter-action.speed} case PUSHARR: state.arr.push(...action.arr) return {...state,arr:[...state.arr]} case POPARR: state.arr.pop() return {...state,arr:[...state.arr]} default: return state } } export default reducer
- 2.2 splitting
import {ADD,REDUCE,PUSHARR,POPARR}from './constants.js' import redux from "redux" const initState={ counter:1, arr:[111,222,333] } function reducerAddReduce(state=initState,action){ switch (action.type) { case ADD: return {...state,counter:state.counter+action.speed} case REDUCE: return {...state,counter:state.counter-action.speed} default: return state } } function reducerArr(state=initState,action){ switch (action.type) { case PUSHARR: state.arr.push(...action.arr) return {...state,arr:[...state.arr]} case POPARR: state.arr.pop() return {...state,arr:[...state.arr]} default: return state } } let reducer =redux.combineReducers({ changeCounter:reducerAddReduce, changeArr:reducerArr }) export default reducer
3. Create store / actioncreate JS file
import { ADD, REDUCE, PUSHARR, POPARR } from "./constants.js"; function add(speed) { return { type: ADD, speed, }; } function reduce(speed) { return { type: REDUCE, speed, }; } function pushArr(arr) { return { type: PUSHARR, arr, }; } function popArr() { return { type: POPARR, }; } export { add, reduce, pushArr, popArr };
4. Create store / constants JS file
const ADD = "ADD"; const REDUCE = "REDUCE"; const PUSHARR = "PUSHARR"; const POPARR = "POPARR"; export { ADD, REDUCE, PUSHARR, POPARR };
5. In Src / index JS file
import store from './store/index.js' import {add,reduce,pushArr,popArr} from './store/actionCreators.js' store.subscribe(()=>{ console.log(store.getState()); //Example of printing without splitting reducer: {counter: 2, arr: [111, 222, 333, 444, 555]} //Split reuder Print example:{ //changeCounter: { counter: 2, arr: [ 111, 222, 333, 444, 555 ] }, //changeArr: { counter: 1, arr: [ 111, 222, 333, 444 ] } //} }) setTimeout(() => { store.dispatch(add(2)) }, 1000); setTimeout(() => { store.dispatch(reduce(1)) }, 2000); setTimeout(() => { store.dispatch(pushArr([444,555])) }, 3000); setTimeout(() => { store.dispatch(popArr()) }, 3000);
6. Operation
yarn start
2.3 Redux flow chart
We already know the basic use process of Redux, so we can have a clearer understanding of the actual development process of Redux;
- 1. There is usually only one global Store to Store our State;
- 2. In some cases, the component will distribute actions (these actions are defined in advance)
- 3. The reducer will receive these action s and return a new State in the reducer as the State of the Store;
- 4. After the state is updated, a notification will be triggered to inform the subscriber that the data has changed;
- 5. The subscriber gets the latest data (props) and updates it to jsx, and the interface changes