Article directory
- Basics
- What is Redux?
- Install Redux
- Core idea
- Three principles
- Action
- Reducer
- Store
- Basic structure of State
- `Use of react Redux
- senior
- Asynchronous Action
- Normalized data
- Why do you want to set up a normalized state data structure?
- Design normalized State data structure
- Inter table relationship
- Nested data normalization
- Manage normalized data
- Middleware usage
- Reference
Basics
What is Redux?
Redux is a JavaScript state container that provides predictable state management. You can build consistent applications that run in different environments.
Install Redux
npm install --save redux #perhaps yarn add redux
Core idea
The core idea of Redux is to update state through action.
Action is like an indicator of what happened. Finally, in order to string action and state, some functions are developed, which are called reducers. reducer is just a function that receives state and action and returns the new state.
For large applications, it's impossible to write only one such function, so we write many small functions to manage a part of state separately. Finally, we call these small functions through a large function to manage the state of the whole application.
Three principles
Single data source
The state of the whole application is stored in an object tree, and the object tree only exists in a single store.
console.log(store.getState()) /* output { visibilityFilter: 'SHOW_ALL', todos: [ { text: 'Consider using Redux', completed: true, }, { text: 'Keep all state in a single tree', completed: false } ] } */
State read-only
The only way to change the state is to trigger action, which is a common object used to describe events that have occurred.
This ensures that neither the view nor the network request can directly modify the state. Instead, they can only express the intention to modify. Because all the changes are centralized and executed in strict order one by one, there is no need to worry about the occurrence of race conditions.
Action s are just ordinary objects, so they can be printed, serialized, stored, later debugged, or played back by tests.
//Define the Action object and trigger the Action through the store.dispatch method store.dispatch({ type:'COMPLETE_TODO', index:1 }) store.dispatch({ type:'SET_VISIBILITY_FILTER', filter:'SHOW_COMPLETED' })
Using pure functions to perform modifications
To describe how action changes state tree, you need to write the reducers function
Reducer is just a pure function that takes the previous state and action and returns the new state.
At the beginning, you may only have one reducer. As the application grows larger, you can split it into several small reducers to operate different parts of state tree independently, because reducers are only functions. You can control the order in which they are called, pass in additional data, and even write reusable reducer functions to handle some common tasks.
//1. Import the combineReducers and createStore objects in redux import {combineReducers,createStore} from 'redux'; //2. Define several small reducer functions to handle specific state (parameters are previous state and action) function visibilityFilte(state = 'SHOW_ALL' , action){ switch(action.type){ case 'SET_VISIBBILITY_FILTER': return action.filter default: return state } } function todos(state = [] , action){ switch(action.type){ case 'ADD_TODO': return [ ...state, { text:action.text, completed:false, } ] case 'COMPLETED_TODO': return state.map((todo,index)=>{ if(index === action.index){ return Object.assign({},todo,{ completed:true }) } return todo }) default: return state } } //3. Combine several small reducers through the combinedreducers function. let reducer = combineReducers({visibilityFilte,todos}) //4. Create a Redux Store object through the reducer function to store the application status let store = createStore(reducer); //5. You can subscribe to updates manually or bind events to view layers store.subscribe(()=>{ //When the state update triggers here console.log(store.getState()); }); //6. Use store to specify action to trigger action to change state, store.dispatch({ type:'COMPLETE_TODO', index:1 }) store.dispatch({ type:'SET_VISIBILITY_FILTER', filter:'SHOW_COMPLETED' })
Action
-
brief introduction
Action is the payload that transfers data from the application (not called View here because it may be the data from the server response, user input or other non View data) to the store. It is the only source of store data. In general, you will pass the action to the store through store.dispatch(). (in short: action is used to describe what happened)
The action of adding a new todo task is as follows:
const ADD_TODO = 'ADD_TODO' { type:ADD_TODO, text:'Build my first Redux app' }
Action is essentially a common object of JavaScript. We agree that a string type type type field must be used within an action to represent the action to be performed. In most cases, type is defined as a string constant. When the application scale is growing, it is recommended to use independent modules or files to store actions.
import {ADD_TODO,REMOVE_TODO} from '../actionTypes'
In addition to the type field, the structure of the action object is entirely up to you. Refer to Flux standard Action Get advice on how to construct an action.
At this time, we need to add another action index to represent the action sequence number of the user completing the task. Because the data is stored in an array, we refer to a specific task by subscribing the Index. In the actual project, the unique ID will be generated as the reference ID when creating new data.
{ type:TODO_ADD, index:5 }
We should try to minimize the data transfer in action. For example, in the above example, it is better to pass the index than the whole task object
-
Action Creator
Action creating function is the method to generate action. The two concepts of action and action creation function are easy to mix, so it is better to distinguish them when using them.
The action creation function in Redux simply returns an action:
function addTOdo(text){ return { type:ADD_TODO, text } }
This will make the functions created by action easier to migrate and test.
The dispatch() method can be called directly from store.dispatch() in store, but most of the time you will use the connect() helper provided by react-redux. bindActionCreators() can automatically bind functions created by multiple actions to the dispatch() method.
Note: we usually use this method (action factory function / action creator) to construct the action object).
-
Source case (actions.js)
//action type (large projects are usually declared in one component independently, and exported for other components) export const ADD_TODO = 'ADD_TODO'; export const TOGGLE_TODO = 'TOGGLE_TODO'; export const SET_VISIBILITY_FILTER = 'SET_VISIBILITY_FILTER'; //Other constant objects export const VisibilityFilters = { SHOW_ALL:'SHOW_ALL', SHOW_COMPLETED:'SHOW_COMPLETED', SHOW_ACTIVE:'SHOW_ACTIVE' } //Create action function and export, return action export function addTodo(text){ return { type:ADD_TODO, text } } export function toggleTodo(index){ return { type:TOGGLE_TODO, index } } export function setVisibilityFilter(filter){ return { type:SET_VISIBILITY_FILTER, filter } }
Reducer
-
brief introduction
Reducers specifies how the application status responds to actions and sends them to the store. Remember that actions only describes the fact that something happened, not how the application updates the state. (in short: the reducer updates the state according to the action)
There is only one single reducer function in the whole application: this function is the first parameter passed to createStore. Finally, a single reducer needs to do the following:
- When the reducer is called for the first time, the value of state is undefined. The reducer needs to provide a default state to handle this situation before the action is passed in.
- The reducer needs the previous state and dispatch action s to decide what needs to be done.
- Assuming that you need to change the data, you should create new objects or arrays with the updated data and return them.
- If there are no changes, you should return the current state itself.
Note: it is very important to keep the reducer pure. Never do these operations in the reducer
- Modify the incoming parameters;
- Perform side-effect operations, such as request and route jump;
- Call non pure functions such as Date.now() or Math.random().
Just remember that reducer must be pure. As long as the incoming parameters are the same, the next state calculated by the return must be the same. There is no special case, no side effect, no API request, no variable modification, and only calculation is performed.
-
Action processing
When Redux executes for the first time, the state is undefined. At this time, we can take the opportunity to set and return the initial state of the application.
//Introducing VisibilityFilters constant object import {VisibilityFilters} from './actions' //Initialize the state state. const initialState={ visibilityFilter:VisibilityFilters.SHOW_ALL, todos:[] }; //Define the reducer function function todoApp(state,action){ //If state is defined to return the initialized state if(typeof state === 'undefined'){ return initialState; } //No action will be processed here, only the incoming state will be returned return state }
Use ES6 parameter default syntax to simplify code
function todoApp(state = initialState,action){ //No action will be processed here, only the incoming state will be returned return state; }
You can now process action.type set? Visibility? Filter. All you have to do is change the visibilityFilter in state:
function todoApp(state = initialState,action){ switch(action.type){ case 'SET_VISIBILITY_FILTER': return Object.assign({},state,{ visibilityFilter:action.filter }) default: return state; } }
Be careful:
-
Do not modify state
A new copy is created using Object.assign(). Do not use the following methods
Object.assign(state,{visibilityFilter:action.filter}), because it changes the value of the first parameter. You must set the first parameter to an empty object.
-
Returns the old state in the case of default. When an unknown action is encountered, be sure to return the old state
Object.assign(target,source1,source2...sourceN)
yes ES6 Property for merging objects.//Object merging: source1, source2 are merged into target. const target = { a: 1 }; const source1 = { b: 2 }; const source2 = { c: 3 }; Object.assign(target, source1, source2); target // {a:1, b:2, c:3} //Replacement of attribute with the same name: the value of attribute a in source replaces the value of attribute a in target. const target = { a: { b: 'c', d: 'e' } } const source = { a: { b: 'hello' } } Object.assign(target, source)// { a: { b: 'hello' } } //Array processing: assign will treat the array as an object with property names 0, 1, 2, //So the value 4 of the 0 property of the source array overrides the value 1 of the 0 property of the target array. Object.assign([1, 2, 3], [4, 5])// [4, 5, 3]
Be careful:
- The first parameter of this method is the target object, and the later parameters are all the source objects;
- If the target object has an attribute with the same name as the source object or multiple source objects, the following attributes will overwrite the previous ones;
- Object.assign is a shallow copy. If the value of a property of the source object is an object, the copy of the target object will get the reference of the object. Any change of the object will be reflected on the target object;
- Replacement of a property with the same name. For nested objects, once an attribute with the same name is encountered, the processing method of Object.assign is replacement, not addition.
-
-
Handling multiple actions
Be careful:
- Each reducer is responsible for managing only the part of the global state that it is responsible for. The state parameters of each reducer are different, corresponding to the part of state data it manages.
Last. Redux provides** combineReducers() **The working class is used to generate a function that calls your series of reducers. Each reducer filters out part of the state data and processes it according to their key. Then the generated function merges all the results of the reducers into a large object.
-
Code case (reducers.js)
import {combineReducers} from 'redux' //Import action type constants and functions from actions file import { ADD_TODO, TOGGLE_TODO, SET_VISIBILITY_FILTER, VisibilityFilters } from './actions' //Destructuring assignment const {SHOW_ALL} = VisibilityFilters //Define the reducer function to display the filter function visibilityFilter(state = SHOW_ALL,action){ switch(action.type){ case SET_VISIBILITY_FILTER: return action.filter default: return state } } //Define the reducer function to deal with things function todos(state = [] , action){ switch(action.type){ case ADD_TODO: return [ ...state, { text:action.text, completed:false } ] case TOGGLE_TODO: return state.map((todo,index)=>{ if(index ==== action.index){ return Object.assign({},todo,{ completed:!todo.completed }) } return todo }) default: return state } } //Associate multiple customized reducers through the combinedreducer function const todoApp = combineReducers(){ visibilityFilter, todos } //Export the Reducer for external use export default todoApp;
Store
-
brief introduction
Action is described earlier to describe "what happened", and reducers are used to update state based on action.
Store is the object that connects action and reducer. The store has the following responsibilities:
- Maintain the state of the application;
- Provide getState () method to get state;
- Provide the dispatch (action) method to update the state;
- Register the listener through subscribe (listener);
- The unsubscribe function returned through the subscribe (listener) is used to unregister the listener.
Again, Redux should have only one single store. When you want to split the data processing logic, you should use the reducer combination instead of creating multiple stores.
-
It's very easy to create a Store based on the existing reducers. In the previous section, we used combinedreducers () to merge multiple reducers into one. Now we import it and use it to create Store (reducers):
import {createStore} from 'redux' //Import the defined reducers object import todoApp from './reducers' //Create Store object let store = createStore(todoApp)
The second parameter of createStore() is optional and is used to set the initial state of the state. This is very useful for developing homogeneous applications. The state structure of the server-side redux application can be consistent with that of the client. Then the client can directly use the server-side state received from the network for local data initialization:
let store createStore(todoApp,window.STATE_FROM_SERVER)
-
Initiate Action (index.js)
After the above steps, we have created action, reducer and store. At this time, we can verify that although there are no pages, because they are pure functions, we only need to call them to judge the return value. Writing tests is that simple.
import {createStore} from 'redux' //1. Introduce the defined action import { addTodo, toggleTodo, setVisibilityFilter, VisibilityFilters} from './actions' //2. Introduce the defined reducer import todoApp from './reduces' //3. Create a store let store = createStore(todoApp) //4. Trigger action to update state through reducer store.dispatch(addTodo('Learn about actions')) store.dispatch(addTodo('Learn about reducers')) store.dispatch(addTodo('Learn about store')) store.dispatch(toggleTodo(0)) store.dispatch(toggleTodo(1)) store.dispatch(setVisibilityFilter(VisibilityFilter.SHOW_COMPLETED)) //5. Enable listening state update through subscribe. Note that a function object is returned to log off listening const unsubscribe = store.subscribe(()=>{ console.log(store.getState()) }) //6. Stop listening for state updates unsubscribe();
Basic structure of State
Redux encourages you to think about your application based on the data you need to manage. Data is your application state.
The top-level state tree in Redux state is usually a common JavaScript object (of course, it can also be other types of data, such as numbers, data or other special data structures, but the top-level values of most libraries are common objects).
Most applications handle a variety of data types, which can be divided into the following three categories:
- Domain data: data to be displayed, used or modified by application;
- App state: data specific to an application's behavior;
- UI state: controls how the UI presents data.
A typical application state will be roughly as follows:
{ domainData1:{}, //Data domain 1 domainData2:{}, //Data domain 2 appState1:{}, //Application status domain 1 appState2:{}, //Application status domain 1 ui:{ //UI domain uiState1:{}, uiState2:{}, } }
Use of react Redux
It is emphasized here that there is no relationship between Redux and React. Redux supports React, Angular, Ember, JQuery, and even pure JavaScript.
Although this time, Redux works best with React and Deku, which allow you to describe the interface in the form of state functions. Redux initiates state changes in the form of action.
Install React Redux
Redux does not contain React binding library , which needs to be installed separately.
npm install --save react-redux #perhaps yarn add react-redux
Core API explanation
1. Provider
The provider component is the core component provided by react Redux, which provides the Redux Store for internal component access.
Use
Provider components provided through react Redux include other components
//Import Provider component import {Provider} from 'react-redux'; //Import the created Redux Store object import store from './store'; //Import components import CustomComponent from './CustomComponent'; export default class App extends Component{ render(){ return( <Provider> <CustomComponent/> </Provider> ); } }
2. connect
The connect component is also the core component provided by react redux. It is used to associate the current component with the Redux Store, so as to map the current component Props with the State and dispatch in the Redux Store through mapStateToProps and mapDispatchToProp functions.
Be careful:
- Before using connect(), you need to define mapStateToProps;
- Components connected by connect need to be wrapped by Provider components;
- mapStateToProps: this function specifies to establish a mapping relationship between the current component Props and Redux store state. Every time the state of the Store changes, the function of all components in the application will be called. If the component is not transferred, it will not listen to the change of Store State, that is to say, the update of Store will not cause the update of UI.
- mapDispatchToProps: this function specifies to map the current component props to store.dispatch. If you don't transfer react Redux, the dispatch will be automatically injected into the props of the component (you can use this.props.dispatch(action)).
Use
The components wrapped in the Provider component are connected to the Redux Store through the connect provided by react redux.
//1. Introduce connect import { connect } from 'react-redux'; class CustomComponent extends Component{ render(){ return ( <View style={{flex: 1, flexDirection: 'column', alignItems: 'center'}}> <Button title={'Modify display data'} /** *Indirectly trigger store.dispatch(action) through this.props.btnOnClick *Change the State in the Store. */ onPress={() => {this.props.btnOnClick();}}/> //Get the data in Redux Store State through property mapping this.props.showText. <Text style={{marginTop: 20}}>{this.props.showText}</Text> </View> ); } } /** *2.Create mapStateToProp function */ const mapStateToProps = (state) => { return { /** *Map loginText in Redux Store State to the showText property of the current component, *Then the current component can obtain the value of loginText stored in the Store State through this.props.showText. */ showText: state.loginText, //Map the loginStatus state in the Redux Store State to the showStatus property of the current component. showStatus: state.loginStatus, } }; /** *3.Create mapDispatchToProp function */ const mapDispatchToProps = (dispatch)=>{ return{ /** *changeShowTex: For the custom props attribute function name, the current component uses this.props.changeShowText() *You can trigger store.dispatch(action) to update the data in Redux Store State. */ changeShowText:()=>{ dispatch(action); //Send the specified action to change the State in the Store } } } /** *4.Connect the current component to the Store of Redux through the connect function. (the current component needs to be wrapped by the Provider component) */ export default connect(mapStateToProps,mapDisPatchToProps)(CustomComponent);
Full sample code
To achieve the goal: click the page button (trigger store.dispatch(action)) to change the currently displayed copy information (the copy display information here is stored in the Redux Store State)
-
Define Action type
//ActionType.js //Landing status const LOGIN_TYPE = { LOGIN_SUCCESS: 'LOGIN_SUCCESS', LOGIN_FAILED: 'LOGIN_FAILED', LOGIN_WAITING: 'LOGIN_WAITING', }; export {LOGIN_TYPE};
-
Create Action (tell Reducer what to do)
//Actions.js //Import action types import {LOGIN_TYPE} from '../ActionType'; export const loginWaiting = { type: LOGIN_TYPE.LOGIN_WAITING, text: 'Landing...', }; export const loginSuccess = { type: LOGIN_TYPE.LOGIN_SUCCESS, text: 'Landing successfully...', }; export const loginFailed = { type: LOGIN_TYPE.LOGIN_FAILED, text: 'Landing failed...', };
-
Create a Reducer (process the corresponding logic according to the passed action type to return a new state)
//Reducer.js //Import action type import {LOGIN_TYPE} from '../ActionType'; //Default state const defaultState = { loginText: 'Content display area', loginStatus: 0, }; /** *To create a reducer: *Change loginText and loginStatus in the Store State according to the current action type, *The mapStateToProps function that sends the store.dispatch(action) event component is called back. */ const AppReducer = (state = defaultState, action) => { switch (action.type) { case LOGIN_TYPE.LOGIN_WAITING: return { loginText: action.text, loginStatus: 0, }; case LOGIN_TYPE.LOGIN_SUCCESS: return { loginText: action.text, loginStatus: 1, }; case LOGIN_TYPE.LOGIN_FAILED: return { loginText: action.text, loginStatus: 2, }; default: return state; } }; export {AppReducer};
-
Create Redux Store
//store.js import {createStore} from "redux"; import {AppReducer} from '../reducers/AppReducer'; //Create store according to Reducer const store = createStore(AppReducer); export default store;
-
Use entry
//App.js //Pass the store to the sub component access through the Provider component provided by react Redux import React,{Component} from 'react'; import {Provider} from 'react-redux'; import store from './store'; import CustomComponent from '../page/CustomComponent'; import CustomComponent2 from '../page/CustomComponent2'; import CustomComponent3 from '../page/CustomComponent3'; export default class App extends Component{ render(){ return( <Provider store={store}> <CustomComponent/> //Sub components <CustomComponent2/> //Sub module 2 <CustomComponent3/> //Sub module 3 ... </Provider> ) } }
Above, we use the Provider component provided by react Redux to provide our created store to the subcomponent CustomComponent for access. Next, we will see how to establish a relationship with the Redux Store in the subcomponent and access its State content.
-
Access to Redux Store State data in the subcomponent (take CustomComponent as a case, and other subcomponents are the same)
//CustomComponent.js import React,{Component} from 'react'; import {View,Text,Button} from 'react-native'; //Import Action. Click the following business to trigger dispatch(action) import * as Actions from '../actions/Actions'; //1. Import connect. You need to connect the current component and Redux Store through the connect. import {connect} from 'react-redux'; export default class CustomComponent extends Component{ render(){ return( <View style={{flex: 1, flexDirection: 'column', alignItems: 'center'}}> /** *5.The current component is obtained by specifying the custom property in the mapStateToProps function through this.props.xxx * Value mapped from Redux Store State. */ <Text style={{marginTop: 20}}>{this.props.showText}</Text> <Button title={'Simulated landing'} onPress={() => { /** *6.The current component calls the mapDispatchToProps customized function through this.props.xxx(), *In this way, the store.dispatch(action) is indirectly triggered to send the action to update the State in the Store */ //When the second parameter of connect is not passed, Redux Store will automatically map the dispatch to Props. //this.props.dispatch(Actions.loginWaiting);} this.props.btnOnClick(1);} }/> <Button title={'Simulated Login succeeded'} onPress={() => { this.props.btnOnClick(1);} }/> <Button title={'Simulated login failed'} onPress={() => { this.props.btnOnClick(2);} }/> </View> ) } } /** *2.Define mapStateToProps function (when the State in the Store changes, it will call back to change the function) * Returns an Object. Internally, the value in state is mapped to a custom attribute so that the current component can use this.props.xxx * Get data in State. */ const mapStateToProps = (state)=>{ return{ //Map loginText in Redux Store State to the custom showText property. showText:state.loginText, //Map the loginStatus in the Redux Store State to the custom showStatus property. showStatus:state.loginStatus, } } /** *3.Define mapDispatchToProps function: * Returns an Object, the name of the internally defined property function, so that the current component can call this.props.xxx() * To indirectly trigger store.dispatch(action). */ const mapDispatchToProps = (dispatch)=>{ return { changeShowText:(type)=>{ switch(type){ case 0: dispatch(Actions.loginWaiting); break; case 1: dispatch(Actions.loginSuccess); break; case 2: dispatch(Actions.loginFailed); break; } } } } /** *4.Connect the current CustomComponent component with the Redux Store through connect, and use mapStateToProps * mapDispatchToProps Function to map the Redux Store State to the current component Props. */ export default connect(mapStateToProps,mapDispatchToProps)(CustomComponent);
Extension:
1. Accessing Redux Store State in nested components
The following components:
Root component APP.js
return( <Provider> <CustomComponent/> </Provider> )
Subcomponent CustomComponent.js
//Internal introduction of Child components return( ... <Child/> )
If we want to access the Redux Store State in the Child component, the access mode is the same as that of the CustomComponent component, as follows:
Child.js
import React,{Component} from 'react'; import {View, Text, Button} from 'react-native'; //1. Import connect import {connect} from 'react-redux'; import * as Actions from '../actions/CommonAction'; export default class Child extends Component{ render(){ return( <View> /** *Use: specify the property name defined by mapStateToProps through this.props.xxxx *Gets the data for the Store State map. */ <Text>{this.props.xxxx}</Text> <Button onPress={()=>{ /** *Call the function declared by mapStateToProps through this.props.xxxx() *Indirectly trigger store.dispatch(action) to update Redux Store State. */ this.props.xxx(); }}> </View> ); } //2. Define mapStateToProps function const mapStateToProps = (state)=>{ return{ //TODO... } } //3. Define mapDispatchToProps function const mapStateToProps = (dispatch)=>{ return{ //TODO... } } /** *4.Connect the current component with Redux Store through connect, and use mapStateToProps and mapStateToProps functions *Map Store State and dispatch to Props */ export default connect(mapStateToProps,mapStateToProps)(Child); }
2. Use combinedreducers to merge multiple scattered reducers
In the above code, our actions and reducers are all defined in one file. It is difficult to troubleshoot and maintain errors in the later stage of large and medium-sized projects. Therefore, we reconstruct the project, split the actions and reducers according to the business functions to make them independent, and merge multiple reducers with the help of combinedreducers.
For example, we have landing and registration pages, so we split the original Action into LoginAction and RegisterAction, and split the original Reducer into LoginReducer and RegisterReducer to perform their respective duties and handle related businesses.
-
Split Action
LoginAction.js
/** *Landing Action *PS:At present, the action trigger carries static data, and the internal data is written, *Later, it will expand the interface request to return data and fill it into data */ import {LOGIN_TYPE} from './ActionType'; export const loginWaiting = { type: LOGIN_TYPE.LOGIN_WAITING, data: { status: 10, text: 'Landing...', }, }; export const loginSuccess = { type: LOGIN_TYPE.LOGIN_SUCCESS, data: { status: 11, text: 'Landing successfully!', }, }; export const loginFailed = { type: LOGIN_TYPE.LOGIN_FAILED, data: { status: 12, text: 'Landing failed!', }, };
RegisterAction.js
/** *Registered Action */ import {REGISTER_TYPE} from './ActionType'; export const registerWaiting = { type: REGISTER_TYPE.REGISTER_WAITING, data: { status: 20, text: 'In registration...', }, }; export const registerSuccess = { type: REGISTER_TYPE.REGISTER_SUCCESS, data: { status: 21, text: 'login was successful!', }, }; export const registerFailed = { type: REGISTER_TYPE.REGISTER_FAILED, data: { status: 22, text: 'login has failed!', }, };
-
Split Reducer
LoginReducer.js
//Default landing page properties const defaultLoginState = { Ui: { loginStatus: '', //Login status (used to control whether the login button can be clicked, and display the loading box, etc.) loginText: '', //The text of prompt in different login States }, }; const LoginReducer = (state = defaultLoginState, action) => { switch (action.type) { case LOGIN_TYPE.LOGIN_WAITING: return { ...state, Ui: { loginStatus: action.data.status, loginText: action.data.text, }, }; case LOGIN_TYPE.LOGIN_SUCCESS: return { ...state, Ui: { loginStatus: action.data.status, loginText: action.data.text, }, }; case LOGIN_TYPE.LOGIN_FAILED: return { ...state, Ui: { loginStatus: action.data.status, loginText: action.data.text, }, }; default: return state; } }; export default LoginReducer;
RegisterReducer.js
//Registration page default status const defaultRegisterState = { Ui: { registerStatus: '', //Login status (used to control whether the login button can be clicked, and display the loading box, etc.) registerText: '', //The text of prompt in different login States }, }; const RegisterReducer = (state = defaultRegisterState, action) => { switch (action.type) { case REGISTER_TYPE.REGISTER_WAITING: return { ...state, Ui: { registerStatus: action.data.status, registerText: action.data.text, }, }; case REGISTER_TYPE.REGISTER_SUCCESS: return { ...state, Ui: { registerStatus: action.data.status, registerText: action.data.text, }, }; case REGISTER_TYPE.REGISTER_FAILED: return { ...state, Ui: { registerStatus: action.data.status, registerText: action.data.text, }, }; default: return state; } }; export default RegisterReducer;
Combine the reducers through the combinedreducer ({key1: reducer1, key2: reducer2})
Be careful:
- When using combinedreducers to combine reducers, we can specify the Reducer name key or omit it (the component name exported by Reducer is used by default).
- After combinedreducers are combined, when mapping through mapStateToProps function in the presentation component, we need to specify the Reducer name specified during combinedreducers merging to access the data in the Redux Store State (see the following case)
//Merge Reducer import LoginReducer from './LoginReducer'; import RegisterReducer from './RegisterReducer'; const AppReducers = combineReducers({ LoginReducer, //LoginReducer name is not specified. LoginReducer is used by default registerReducer:RegisterReducer,//Specify LoginReducer as registerReducer, }); export default AppReducers;
The use of subsequent reducers remains the same, and the Store is created by specifying AppReducers to use createStore.
After reconstruction, run it to see the data format in our State as follows:
/** *Through the combinedreducers, in the Redux Store State *Automatically add names to different reducers to distinguish their data status areas */ { "LoginReducer": { "Ui": { "loginStatus": 10, "loginText": "Landing..." } }, "registerReducer": { "Ui": { "registerStatus": "", "registerText": "" } } }
Next, we map the Redux Store State to the Props of the display component in mapStateToProps
Landing page / component (Login.js)
//Define the first parameter of the connect function: map the state in the Store to the props of the current component const mapStateToProps = (state) => {return { /** *Here, we map the properties in the query Redux Store State to the Props properties of the current component through state.xxx. *Where [xxx] is the name specified when combinedreducers merges reducers (no default component export name is specified) */ loginShowText: state.LoginReducer.Ui.loginText, loginShowStatus: state.LoginReducer.Ui.loginStatus, }; };
Registration page / component (Register.js)
//Define the first parameter of the connect function: map the state in the Store to the props of the current component const mapStateToProps = (state) => { return { /** *Use state.xxxx to specify the name specified when combinedreducers merge reducers, *Map the Redux Store State property to the property of the presentation component. */ registerShowText: state.registerReducer.Ui.registerText, registerShowStatus: state.registerReducer.Ui.registerStatus, }; };
The restructured project structure is as follows, so we can deal with Action and business logic in Reducer according to the business module to make the logic clearer and improve the readability and maintainability of the project.
3. Use bindActionCreators to simplify the distribution of actions
What is bindActionCreators?
The function of bindActionCreators is to convert single or multiple action creators into the function set form of dispatch(action) in the connectmapDispatchToProps function that uses the connect of redux to associate react with redux store. Instead of manually dispatching (actioncreator (type)), developers can call methods directly.
bindActionCreators principle
bindActionCreators is actually a function that encapsulates the part of the dispatch that is directly combined with a single or multiple action creator s and then sent out. bindActionCreators will send this function using dispatch.
Use vs. do not use bindActionCreators
If we create an action through action creator
UserAction.js
//Add user's synchronization action export const addUser = (user) => { return { type: ADD_USER, user, }; }; //Delete user's synchronization action export const removeUser = (user)=>{ return { type: REMOVE_USER, user, }; } //Calculate total users export const sumUser = () => { return { type: SUM_USER, }; };
Then use in TestComponent.js through mapDispatchToProps function:
-
Do not use bindActionCreators
//1. Import UserAction import {addUser,removeUser,sumUser} from './actions/UserAction'; //2. Define mapDispatchToProps function of connect const mapDispatchToProps = (dispatch)=>{ return{ propsAddUser:(user)=>{ //Distribute the specified Action through dispatch dispatch(addUser(user)) }, propsRemoveUser:(user)=>{ dispatch(removeUser(user)) }, propsSumUser:()=>{ dispatch(sumUser()) } } } //connect to associate react with Redux Store and export components export default connect(mapStateToProps,mapDispatchToProps)(TestComponent) //3. Call the mapped props defined in mapDispatchToProps in the component <Button title='Add user' onpress={()=>{ let user={ name:'zcmain', age:20, address:'Shanghai China' } //Just call the properties defined in mapDispatchToProps through this.props.xxx. this.props.propsAaddUser(user); //this.props.propsRemoveUser(user); //this.props.propsSumUser(); }}>
-
Using bindActoinCreators
Format:
bindActionCreators(actionCreators,dispatch)
Parameters:
- actionCreators: (function or object): it can also be an object. All elements of this object are action create functions.
- Dispatch: (function): the function available on the Store instance dispatch.
Example:
//1. Import UserAction import {addUser,removeUser,sumUser} from './actions/UserAction'; //2. Import bindActionCreators in redux import {bindActionCreators} from 'redux'; //3. Define mapDispatchToProps function of connect const mapDispatchToProps = (dispatch)=>{ return{ /** *Use bindActionCreators to convert multiple action creator s into dispatch(action) form, *disptch(action) is not called manually here. */ actions:bindActionCreators({ propsAddUser:(user)=>addUser(user), propsRemoveUser:(user)=>removeUser(user), propsSumUser:sumUser, //action creator without parameters },dispatch), } } //connect to associate react with Redux Store and export components export default connect(mapStateToProps,mapDispatchToProps)(TestComponent) //4. Call the mapped props defined in mapDispatchToProps in the component <Button title='Add user' onpress={()=>{ let user={ name:'zcmain', age:20, address:'Shanghai China' } /** *Specify calling mapDispatchToProps through this.props.actions.xxxx *bindActionCreators Defined props. */ this.props.actions.propsAddUser(user); //this.props.actions.propsRemoveUser(user); //this.props.actions.propsSumUser(); }}>
**(recommended) import * as xxx to import all action creator s in a file
//1. Import all action creator s in UserAction import * as UserActions from './actions/UserAction'; //2. Import bindActionCreators in redux import {bindActionCreators} from 'redux'; //3. Define mapDispatchToProps function of connect const mapDispatchToProps = (dispatch)=>{ return { /** *Convert all action creator s in UserAction.js to dispatch(action) through bindActionCreators *Form, the disptch(action) is not called manually here. */ actions:bindActionCreators(UserActions,dispatch); } } //Associate react with Redux Store through connect export default connect(mapStateToProps,mapDispatchToProps)(TestComponent) //4. Call the mapped props defined in mapDispatchToProps in the component <Button title='Add user' onpress={()=>{ let user={ name:'zcmain', age:20, address:'Shanghai China' } //Specify to call the specific action in UserAction.js through this.props.actions.xxxx. this.props.actions.addUser(user); //this.props.actions.removeUser(user); //this.props.actions.sumUser(); }}>
How to use bindActionCreator for asynchronous Action
When we use Redux thunk to implement asynchronous Action by creating return functions, how to use asynchronous Action in bindActionCreators? In fact, it is not very different from synchronous Action. If the function called by asynchronous Action has a return value, and after binding the secondary asynchronous function through bindActionCreator, we will directly accept the return value when calling the asynchronous function through this.props.xxxx (xxx is the function name).
Asynchronous Action (UserAction.js)
//Create Action by creating a return function const addUser = (user)=>{ return (dispatch)=>{ //Asynchronous Action has return value return await new Promise((resolve, reject) => { //Simulate asynchronous network requests setTimeout(() => { dispatch(changeLoginBtnEnable(true)); let userInfo = { userId: 10001, realName: 'zcmain', address: 'Shanghai China', }; dispatch(updateUserInfoVo(userInfo)); resolve('success'); }, 2000); }); } }
Bindactioncreators to bind asynchronous actions
... import {UserActions} from './actions/UserAction'; const mapDispatchToProps = (dispatch)=>{ return{ //Convert action creator to dispatch(action) through bindActionCreatros actions:bindActionCreatros(UserActions,dispatch); } }
Use asynchronous Action in component
... <Button title='Add user',onPress={()=>{ let user={ id:'1', name:'zcmain', } /** *If the call function of the action creator bound through bindActionCreator has a return value, *Directly accept the return when calling through this.props */ this.props.actions.addUser(user) .then((response)=>{ //TOOD... console.log('Success:' + JSON.stringify(response); },(error)=>{ console.log('Failure:' + error.message); }).catch((exception)=>{ console.log('Exception:' + JSON.stringify(exception)); }); }}/>
Source code analysis of bindActionCreators
- Determine whether the parameter passed in is an object. If it is a function, it will directly return a function that wraps the dispatch;
- If it is an object, generate the package dispatch function according to the corresponding key;
/** *bindActionCreators function *Parameter Description: *@param actionCreators: action create Function, can be a single function, can also be an object, all elements of this object *They are all action create functions; *@param dispatch: store.dispatch Method; */ export default function bindActionCreators(actionCreators, dispatch) { /** *If actionCreators is a function, the bindActionCreator method is called to the action create function *Bind with dispatch. */ if (typeof actionCreators === 'function') { return bindActionCreator(actionCreators, dispatch) } /** *If actionCreators is not an object or actionCreators is empty, an error is reported */ if (typeof actionCreators !== 'object' || actionCreators === null) { throw new Error('bindActionCreators expected an object or a function, + 'instead received ${actionCreators === null ?+ 'null' : typeof actionCreators}.' + 'Did you write "import ActionCreators from" instead of + '"import * as ActionCreators from"?') } //Otherwise, actionCreators is an object that gets the names of all action create functions const keys = Object.keys(actionCreators) //Traverse the actionCreators array object to save the collection after the dispatch and action create functions are bound const boundActionCreators = {} for (let i = 0; i < keys.length; i++) { const key = keys[i] const actionCreator = actionCreators[key] // Exclusion value is not action create of function if (typeof actionCreator === 'function') { // Binding boundActionCreators[key] = bindActionCreator(actionCreator, dispatch) } } //Return the bound object return boundActionCreators } /** *bindActionCreator function */ function bindActionCreator(actionCreator, dispatch) { // The main function of this function is to return a function. When we call the returned function, we will automatically dispatch the corresponding action // In fact, this one can be changed to the following form // return function(...args) {return dispatch(actionCreator.apply(this, args))} return function() { return dispatch(actionCreator.apply(this, arguments)) } }
senior
Asynchronous Action
The actions we created in the previous section are all synchronous. When we dispatch(action), the state will be updated immediately.
Create a synchronization action (an action object is returned):
//Create synchronization action export const syncAddItem = { type:'addItem', text:'Add a piece of data', } //Or it can be created by function (it can receive parameters and return action) export const syncAddItem=(desc)=>{ return{ type:'addItem', text:desc, } } //The State will be updated immediately after the synchronization action is triggered through the store store.dispatch(syncAddItem); store.dispatch(syncAddItem('Add a piece of data'));
For asynchronous action creation, we need to use the** Redux Thunk **Middleware. The action creation function can return functions in addition to the action object. This action creation function becomes thunk.
What is (why) Redux Thunk?
We need to use middleware such as Redux thunk because Redux storage only supports synchronous data flow. So middleware came to rescue! Middleware allows asynchronous data flow, explains any content you assign, and finally returns a common object that allows synchronous Redux data flow to continue. Therefore, Redux middleware can solve many key asynchronous requirements (such as axios requests).
Redux Thunk middleware allows you to write return functions instead of returning action objects. Thunk middleware can be used to dispatch delayed actions, or only when certain conditions are met. The internal function takes the dispatch and getState of the store as parameters.
When action creates a function return function, this function will be executed by Redux Thunk middleWare (the return (dispatch) function returned by the asynchronous action created below will be executed by Thunk Middleware). This function does not need to be kept pure; it can have side effects, including executing asynchronous API requests. This function can also execute dispatch(action), just like the action of dispatch synchronization.
Redux Thunk used in asynchronous Action
1. Create an asynchronous action (a function returned will be called by the Thunk Middleware):
//Create an asynchronous action, export const asyncAction1 = (str) => { //Returns a function that receives the dispatch parameter (this function will be called by the Thunk Middleware), return (dispatch) => { //Specify other operations after 2 seconds, such as triggering dispatch(action) to update State setTimeout(() => { //dispatch(action); console.log(str); }, 2000); }; }; //The storet distributes asynchronous actions through the dispatch method store.dispatch(asyncAction1('asynchronous Action Create function'))
Of course, in addition to receiving the dispatch parameter, the asynchronous Action return function can also accept the getState parameter. According to the state in the getState, we can make logical judgment and execute different dispatch:
//Create an asynchronous action, export const asyncAction1 = (str) => { //Returns a function that receives the dispatch and getState parameters (this function will be called by the Thunk Middleware), return (dispatch,getState) => { //Get the counter property in the State through getState, and if it is even, return not to trigger dispatch const {counter} = getState(); if(counter % 2 === 0){ return; } //Otherwise, specify other operations after 2 seconds, such as triggering dispatch(action) to update State setTimeout(() => { //dispatch(action); console.log(str); }, 2000); }; }; //store calls the dispatch method store.dispatch(asyncAction1('asynchronous Action Create function receive dispatch and getState attribute'))
In addition to receiving dispatch and getState parameters, the asynchronous Action return function can also use Redux Thunk to inject custom parameters with extraargument function:
//Inject custom parameters into asynchronous Action return function through withExtraArgument of Redux Thunk import {createStore,applyMiddleWare} from 'redux'; import thunk from 'redux-thunk'; //Single parameter injection const name ='zcmain'; //Multiple parameters are wrapped into object injection const age = 20, const city = 'ShanHai', const userInfo = { name, age city, } const store = createStore( reducer, //When creating the Store, the user-defined parameters are injected into the asynchronous Action return function through thunk.withExtraArgument applyMiddleware(thunk.withExtraArgument(name,userInfo)), );
//Asynchronous Action const asyncAction1 = ()=>{ /** *The return function takes three parameters, where name and userInfo pass the *Thunk.withExtraArgument(name,userInfo)Custom parameters for injection. */ return (dispatch,getState,name,userInfo)=>{ //TODO...you can use name and userInfo here } }
The function called by the Thunk middleware middleware can have a return value, which is passed as the return value of the dispatch method.
//Create an asynchronous Action export const asyncAction2 = (url) => { //Returns a function that receives the dispatch parameter return (dispatch) => { /** *thunk middleWare The function called returns a Promise object, which is passed as the return value of the dispatch method, *Here, we call dispatch(action) to update State through Fetch network request and response result. *Be careful: * Don't use catch because it will catch any errors in the dispatch and rendering, * Cause 'Unexpected batch number' error. * https://github.com/facebook/react/issues/6895 */ return fetch(url) .then((response) =>{ response.json() },(error)=>{ }) .then((json) => { //Send dispatch(action) to update State after receiving the corresponding dispatch({type:'UPDATA',data:json}); return json; }); }; }; /** *store The asynchronous action is triggered through the dispatch method because the action return function has a return value, *Is passed as the return value of the dispatch method. */ store.dispatch(asyncAction2('http://xxx.xxx.xxx.xxx:xxx/test/json)) .then((json)=>{ //TODO... }).catch(()=>{ //TODO... });
Be careful:
Do not use catch in the function with return value executed by Thunk middleWare, because it will catch any error in dispatch and rendering, Unexpected batch number error.
https://github.com/facebook/react/issues/6895
2. Use asynchronous Action
Above, we have created an asynchronous Action. Next, we need to use the asynchronous Action through the Redux Thunk middleware
-
To install the Redux thunk Library:
npm -i --save redux-thunk #perhaps yarn add redux-thunk
-
Use Redux Thunk to create a store through the applyMiddleWare of Redux:
AppStore.js
//Introducing createStore and applyMiddleWare import {createStore,applyMiddleWare} from 'redux'; //Introducing thunk Middleware import thunk from 'redux-thunk'; //Introduction of reducers import reducers from '../reducers/AppReducers'; //Specify the reducer and middleware to create the store const store = createStore(reducers,applyMiddleWare(thunk)); //Export store export default store;
-
Use of other components
LoginComponent.js
/** *In the mapDispatchToProps function of the login page, the asynchronous action is triggered by the dispatch method, and the log will be printed in two seconds; *Note: mapDispatchToProps function depends on the Provider and connect of react redux. */ const mapDispatchToProps = (dispatch) => { return { onclick: () => { dispatch(LoginAction.asyncAction1('Sending asynchrony action')); }, }; }; //It will print in two seconds LOG str:Sending asynchrony Actoin La...
/** *dispatch Distribute asynchronous asyncAction2 with return value */ const mapDispatchToProps = (dispatch) => { return { onclick: () => { dispatch(LoginAction.asyncAction2('Sending asynchrony action Accept return value')) .then((json)=>{ console.log('dispatch Distribute asynchronously Action And receive the return value:' + json) }); }, }; };
Normalized data
Why do you want to set up a normalized state data structure?
In fact, most of the data processed by programs are nested or interrelated. How to use nested and repeated data (object reuse) in state? For example:
const blogPosts = [ { id : "post1", author : {username : "user1", name : "User 1"}, body : "......", comments : [ { id : "comment1", author : {username : "user2", name : "User 2"}, comment : ".....", }, { id : "comment2", author : {username : "user3", name : "User 3"}, comment : ".....", } ] }, { id : "post2", author : {username : "user2", name : "User 2"}, body : "......", comments : [ { id : "comment3", author : {username : "user3", name : "User 3"}, comment : ".....", }, { id : "comment4", author : {username : "user1", name : "User 1"}, comment : ".....", }, { id : "comment5", author : {username : "user3", name : "User 3"}, comment : ".....", } ] } // and repeat many times ]
The above data structure is relatively complex, and some of the data is repeated. There are also some concerns:
- It is difficult to ensure that all the reused data are updated at the same time: when the data needs to be updated after redundancy, it is difficult to ensure that all the data are updated.
- High nesting complexity: nested data means that the reducer logic has more nesting and higher complexity. Especially when you plan to update deep nested data.
- The immutable data needs to be copied and updated by the ancestor data of the state tree when updating, and the new object reference will cause all UI components connect ed to it to render repeatedly. Although the data to be displayed has not changed in any way, updating the deeply nested data objects will force the completely unrelated UI components to render repeatedly.
Because of this, the recommended way to manage relational data or nested data in Redux Store is to treat this part as a database and store the data in a normal way.
Design normalized State data structure
The normal structure includes the following aspects:
- Any type of data has its own "table" in state;
- Any "data table" should store each item in an object, where the ID of each item is the key and the item itself is the value.
- Any reference to a single project should be based on the ID of the storage project.
- The ID array should be used for sorting.
After the state structure in the above blog example is normalized, it may be as follows:
Extract the author and comments objects and use byId
{ posts : { byId : { "post1" : { id : "post1", author : "user1", body : "......", comments : ["comment1", "comment2"] }, "post2" : { id : "post2", author : "user2", body : "......", comments : ["comment3", "comment4", "comment5"] } } allIds : ["post1", "post2"] }, comments : { byId : { "comment1" : { id : "comment1", author : "user2", comment : ".....", }, "comment2" : { id : "comment2", author : "user3", comment : ".....", }, "comment3" : { id : "comment3", author : "user3", comment : ".....", }, "comment4" : { id : "comment4", author : "user1", comment : ".....", }, "comment5" : { id : "comment5", author : "user3", comment : ".....", }, }, allIds : ["comment1", "comment2", "comment3", "commment4", "comment5"] }, users : { byId : { "user1" : { username : "user1", name : "User 1", } "user2" : { username : "user2", name : "User 2", } "user3" : { username : "user3", name : "User 3", } }, allIds : ["user1", "user2", "user3"] } }
Inter table relationship
Because we regard Redux Store as a database, it is also applicable in many database design rules. For example, for many to many relationships, an intermediate table can be designed to store associated item IDS (often referred to as related tables or associated tables). For consistency, we will also use the same byId and allIds in the actual data item table.
entities:{ authors:{byId:{},allIds:[]}, book:{byId:{},allIds:[]}, authorBook:{ byId:{ 1:{ id : 1, authorId : 5, bookId : 22 }, 2:{ id : 2, authorId : 5, bookId : 15, } }, allIds:[1,2] } }
Nested data normalization
Because API s often send return data in a nested form, the data needs to be converted to a normalized form before it is introduced into the state tree. Normalizr Libraries can help you do this. You can define the type and relationship of the schema and provide the schema and response data to Normalizr, who will output the normal transformation of the response data. The output can be placed in the action for updating the store. For more details on its usage, see the Normalizr documentation.
Manage normalized data
When the data has ID, nesting or association relationship, it should be stored in a normal form: the object can only be stored once, the ID is the key value, and the objects refer to each other through the ID.
Compare Store to database, each item is a separate "table". normalizr,redux-orm This kind of library can provide reference and abstraction in the management of standardized data.
Middleware usage
redux-thunk
Redux Thunk is a middleware that provides asynchronous Action processing in redux. Please refer to the above article for specific use< What is Redux Thunk>
redux-saga
Redux saga is also used to solve the problem of asynchronous interaction in RN, which is consistent with the goal of Redux thunk. The differences are as follows:
-
redux-thunk:
- Introduction: redux launched a MiddleWare, which is easy to use. It allows action creation functions to return functions in addition to action objects, and the return function can accept dispatch and getState as parameters. This function does not need to be pure; it can also have side effects, including executing asynchronous API requests. This function can also dispatch action.
- Advantages: small code, easy to use, suitable for small and light applications.
- Disadvantages: the return function is complex and hard to maintain. Because thunk makes the action creation function return not an action object, but a function, and the internal function can be varied, even more complex, which obviously makes the action not easy to maintain.
-
redux-saga
- Introduction: the description on the official website Redux saga is a library used to manage the application Side Effect (side effects, such as asynchronous data acquisition, browser cache access, etc.), its goal is to make Side Effect management easier, execution more efficient, and test more simple.
- Advantages: avoid callback hell (currently, thunk can also be solved by using async/await), convenient for testing and maintenance, and suitable for large applications.
- Disadvantages: steep learning route, large amount of template code.
In many normal and small to medium applications, use async / await style Redux thunk. It saves you a lot of boilerplate code / Operations / types, and you don't need to switch between many different sagas.ts, or maintain a specific sagas tree. However, if you are developing a large application that contains very complex asynchrony and requires some features, such as concurrent / parallel mode, or high requirements for testing and maintenance (especially in Test Driven Development), then Redux sagas may save your life.
See also Redux-Thunk vs. Redux-Saga>
redux-ignore
Redux ignore can specify the trigger condition of the reducer function (for example, specify a / some actions to trigger the current reducer function).
For reducers merged and split by combinedreducers, all reducers will be called when each action is triggered. JavaScript engine has enough ability to run a large number of function calls per second, and most of the sub reducers only use switch statements, and the default state will be returned for most of the actions. If you are still concerned about the performance of the reducer, you can use a similar Redux ignore tool to ensure that only some actions will call one or several reducers.
-
Install Redux ignore
npm -i --save redux-ignore #perhaps yarn add redux-ignore
-
Configure the combinedreducer to combine the reducers, and specify the actions that can trigger the implementation of the reducers through the filteractions (or ignore the specified actions through ignoreActions)
import {combineReducers} from 'redux'; import {filterActions} from 'redux-ignore/src'; import {reducerA} from './ReducerA'; import {reducerB} from './ReducerB'; //Combine the decentralized reducers into one Reducer through combinedreducers const reducers = combineReducers( reducerA:fileterAction( reducerA, /** *Specifies the action array that can trigger the execution of reducerA, only the actions in the array are dispatch ed, *reducerA Will be called. */ [ 'actionA1', 'actionA2'... ]), reducerB:fileterAction( reducerB, /** *Specifies the action array that can trigger the execution of reducerB. Only the actions in the array are dispatch ed, *reducerA Will be called. */ [ 'actionB1', 'actionB2'... ]); )
redux-router
redux-promise
reselect
Why reselect?
When using react Redux, the mapStateToProps function receives the state of the entire Redux store as props, and then returns an object passed in to the component props. This function is called a selector selector.
Let's look at a mapStateToProps function
// mapStateToProps is a selector, which is called every time a component is updated // [disadvantages] the visibleTodos will be recalculated every time the component is updated. If the calculation amount is large, it will cause performance problems const mapStateToProps = (state) => ({ visibleTodos: selectTodos(state.todos, state.visibilityFilter), visibilityFilter: state.visibilityFilter }); // A state evaluation function export const selectTodos = (todos, filter) => { switch (filter) { case 'SHOW_ALL': return todos case 'SHOW_COMPLETED': return todos.filter(todo => todo.completed) case 'SHOW_ACTIVE': return todos.filter(todo => !todo.completed) } }
Before the connect function was implemented, we knew that the function mapping props was stored. Subscribe(). Therefore, whenever the component is updated, mapStateToProps will be called regardless of whether the state is changed, and mapStateToProps will call the state calculation function selectTodos when calculating the state. The procedure is as follows:
store.subscribe() (registration event) - > call mapStateToProps (a selector, return state) when the state is updated) - > call the state calculation function selectTodos
Then, the problem comes. If the calculation amount of the slicer is large, the recalculation of each update will cause performance problems.
The starting point to solve the performance problem is to avoid unnecessary calculation.
The way to solve the problem: start with selector, mapStateToProps. If the state parameters accepted by selector remain unchanged, the calculation function will not be called, and the previous results will be directly used.
What is reselect?
reselect is actually a middleware of redux. It obtains a new state through the incoming multiple state calculations, and then passes it to the Redux Store. Its main purpose is to perform the intermediate calculation, so that the calculation state is cached, so as to determine whether the calculation function (selectTodos) needs to be called according to the incoming state, instead of being called every time the component is updated, so it is more efficient.