As our needs continue to rise, we need more complex data delivery, more levels of data exchange. So why don’t we just give all the data to a hub, independent of all the components, and the hub distributes the data, so that whatever component needs data, we can easily distribute it to him. There is a library that can help us do this: Redux, which enables centralized state management
When is Redux used?
First, let’s clarify the role of Redux for centralized state management.
Redux is suitable for multi-interaction, multi-data source scenarios. Simple to understand is complex
From a component perspective, we can try Redux when we have the following application scenarios
- When a component’s state needs to be shared
- When a component needs to change the state of another component
- When a component needs to change the global state
In addition, there are many cases that need to be implemented using Redux (not learning hook, maybe there is a better way).
This diagram illustrates the difference between pure React and Redux
Redux workflow
The component sends an action object to the Store by calling the store.dispatch method. When the Store receives the action object, The previous state and incoming actions are sent to the reducer together. After receiving the data, the reducer changes the data and returns a new state to the Store. Finally, the Store changes the state
(Photo from gold Digger Community, deleted)
Redux has three core concepts
1. store
Store is the core of Redux, which is the data center of Redux. We can store any data we want in the Store, and when we need to use the data, we can pull it out of the store. So we need to create a store first, and in Redux we can use the createStore API to create a store
In production, we need to add a store.js file to the redux folder in the SRC directory. In this file, create a Store object and expose it
So we need to expose two methods from Redux
import {
createStore,
applyMiddleware
} from 'redux'
Copy the code
And introduce a Reducer for the count component service
import countReducer from './count_reducer'
Copy the code
Finally, the createStore method is called to expose the Store
export default createStore(countReducer, applyMiddleware(thunk))
Copy the code
Middleware is used here and this article should not cover ~
There are several common built-in methods under the Store object
To get the current store, use the getStore method
const state = store.getState();
Copy the code
In our previous flowchart, we need to derive an Action object from the Store via the Dispatch method in the store
Store. Dispatch (` action object `)Copy the code
Finally, we have a subscribe method, which helps us subscribe to store changes, and its callback is executed whenever the store changes
In order to listen for updates, we can bind the SUBSCRIBE method to the component’s mount life cycle function, but this will be troublesome when we have a large number of components, so we can directly use the SUBSCRIBE function to listen for changes in the entire App component
store.subscribe(() => {
ReactDOM.render( < App /> , document.getElementById('root'))
})
Copy the code
2. action
Actions are the only source of data in the store, and generally we pass actions to the store by calling store.dispatch
The action we need to pass is an object that must have a type value
For example, here we expose a method that returns an Action object
export const createIncrementAction = data => ({
type: INCREMENT,
data
})
Copy the code
When we call it, we return an Action object
3. reducer
In Reducer, we need to specify the operation type of the state and what data updates to make, so this type is necessary.
Reducer will perform corresponding operations on the state according to action instructions and return the state after the operation
Here, we judge the type passed in the received action
export default function countReducer(preState = initState, action) {
const {
type,
data
} = action;
switch (type) {
case INCREMENT:
return preState + data
case DECREMENT:
return preState - data
default:
return preState
}
}
Copy the code
Change the data to return to the new state
Create constant file
In our normal coding, it is possible to have spelling errors, but we will find that spelling errors do not necessarily report errors, so it can be difficult to understand.
In the redux directory, we can create a constant file that defines some of the variables we use in our code, for example
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
Copy the code
Write these two words in the constant file and expose them so that when we need to use them, we can import the file and just use its name
Use INCREMENT directly
Implementing asynchronous Actions
To begin with, we call an asynchronous function directly, which is fine, but why not redux?
incrementAsync = () => {
const { value } = this.selectNumber
const { count } = this.state;
setTimeout(() => {
this.setState({ count: count + value * 1 })
}, 500);
}
Copy the code
We can start by wrapping it into an Action object to call
Export const createIncrementAsyncAction = (data and time) = > {/ / without the introduction of the store, Return (dispatch) => {setTimeout(() => {dispatch(createIncrementAction(data))}, time)}}Copy the code
When we click the asynchronous add operation, we call this function, which receives a delay plus time and the data required for the action. The only difference is that it returns a timer function
But this is obviously an error, and it needs to receive an object by default
If we need to implement an incoming function, then we need to say: you just need to silently help me execute the following function!
This is where middleware comes in, exposing applyMiddleware to perform functions in native Redux and introducing redux-Thunk middleware (manual download required)
import thunk from 'redux-thunk'
Copy the code
Just pass it along with the second argument
export default createStore(countReducer, applyMiddleware(thunk))
Copy the code
Note: Asynchronous actions do not have to be written. You can wait for the results of an asynchronous task before distributing synchronous actions
Redux’s Three principles
Understanding Redux will help us understand react-Redux
The first principle
One-way data flow: Data flows in one direction throughout Redux
UI components – > action – > store – > reducer – > store
Second principle
State read-only: State changes cannot be controlled in Redux by directly changing state. If you want to change state, you must trigger an action. The reducer was implemented using action
The third principle
Pure function execution: Each Reducer is a pure function without any side effects. The reducer returns a new state, and the state change triggers the SUBSCRIBE in the store
Story synopsis
-
Redux is part of the React Family bucket, which attempts to provide a “predictable state management” mechanism for react applications.
-
Redux stores the entire application state in one place, called a store
-
I’m going to keep a state tree in there
-
Components can dispatch actions to stores instead of notifying other components directly
-
Other components can refresh their views by subscribing to states in the Store
Redux core
1 State
State is a collection of data
Can be understood as the raw materials that factories need to process goods
2 action
If the State changes, the View changes. But the user doesn’t have access to the State, only to the View so the change in State must be caused by the View.
An action is an instruction that changes state. There are as many actions as there are operations on state.
Can you think of an action as an indicator of what happened
3 Reducer machining function
Action puts the state into the Reucer processing function after issuing the command, and returns the new state. You can think of it as a machine for processing
4 store
Store can be understood as a total factory with multiple processing machines
let store = createStore(reducers);
Copy the code
A Store is an object that ties them together. The Store has the following responsibilities:
Maintain application state; Provide the getState() method to getState; Provide a dispatch(action) method to update state; Register a listener with subscribe(listener); Unsubscribe the listener via a function returned by subscribe(listener).Copy the code
We can use store.getState() to find out the state of an item in the factory and use store.dispatch to send an action directive.
A classic case
This is a classic case of Redux
-
Define the Reducer function to change state based on the type of action
-
Actions definition directive
-
Create a store using createStore
-
Call store.dispatch() to issue the command to change state
import { createStore } from 'redux'
const reducer = (state = {count: 0}, action) => {
switch (action.type){
case 'INCREASE': return {count: state.count + 1};
case 'DECREASE': return {count: state.count - 1};
default: return state;
}
}
const actions = {
increase: () => ({type: 'INCREASE'}),
decrease: () => ({type: 'DECREASE'})
}
const store = createStore(reducer);
store.subscribe(() =>
console.log(store.getState())
);
store.dispatch(actions.increase()) // {count: 1}
store.dispatch(actions.increase()) // {count: 2}
store.dispatch(actions.increase()) // {count: 3}
Copy the code
We could use store.dispatch directly on the React Component, but that would be inconvenient. We need react-redux in this case
class Todos extends Component {
render(){
return(
<div onCLick={()=>store.dispatch(actions.delTodo()) }>test</div>
)
}
}
Copy the code
React-redux
Redux provides the React binding library. It is efficient and flexible.
1 installation
npm install --save react-redux
Copy the code
2 core
- < Provider store>
- connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
Any component within a Provider (such as Comp here) that needs to use data from State must be “connected” — the product of wrapping “your component (MyComp)” with connect.
This function allows us to bind the data in the store to the component as props.
The simple process is shown below:
The Connect method in React-Redux wraps getState and Dispatch on the store as props for the component.
Change the code that was dispatched directly on the component to the following:
index.js
import React, { Component } from 'react'; import store from '.. /store'; import actions from '.. /store/actions/list'; import {connect} from 'react-redux'; class Todos extends Component { render(){ return( <div onCLick={()=>this.props.del_todo() }>test</div> ) } } export default connect( state=>state, actions )(Todos);Copy the code
The Provider takes the key store and passes it to each child component
React Redux distinguishes components into container components and UI components
- The former handles logic
- The latter is only responsible for display and interaction, and does not handle logic internally. The state is completely controlled externally
Two core
-
Provider
Look at the top component of my code up there four words. Yes, you guessed right. The top component is the Provider. We usually wrap the top component in the Provider component, so that all components can be controlled by react-Redux, but stores must be placed as arguments in the Provider component
<Provider store = {store}> <App /> <Provider> Copy the code
The purpose of this component is for all components to have access to the data in Redux.Copy the code
-
connect
This is the hard part of React-Redux, so let’s explain it in detail
First, remember this line of code:
connect(mapStateToProps, mapDispatchToProps)(MyComponent) Copy the code
mapStateToProps
Map state to props (); map data from Redux to props ();
Here’s an example:
Const mapStateToProps = (state) = > {return {/ / prop: state. | means XXX will be one of the state data is mapped to the props in the foo: state. The bar}}Copy the code
Then you can render using this.props. Foo
class Foo extends Component { constructor(props){ super(props); } render(){return(// this.props. Foo </div>)}} foo = connect()(foo); export default Foo;Copy the code
Then you can finish rendering
mapDispatchToProps
The word translates as props for all kinds of dispatches that you can use directly
Const mapDispatchToProps = (dispatch) => {return {onClick: () => {dispatch({type: 'increatment' }); }}; }Copy the code
class Foo extends Component { constructor(props){ super(props); } render(){return(<button onClick = {this.props. OnClick}> increase</button>)}} Foo = connect()(Foo); export default Foo;Copy the code
You can call dispatch directly with this.props. OnClick, so you don’t need to store
That concludes the basic introduction to React-Redux
How does Connect work?
Connect () takes four parameters, mapStateToProps, mapDispatchToProps, mergeProps, and options.
1 mapStateToProps This function allows us to bind data in a store to components as props.
reducer.js
export default function (state = { lists: [{text:' mobile plan '}],newType:'all'}, action) {switch (action.type) {case types.add_todo: return {... state,lists:[...state.lists,{text:action.text}]} case types.TOGGLE_TODO: return {... state,lists:state.lists.map((item,index)=>{ if(index == action.index){ item.completed = ! item.completed } return item })} case types.DEL_TODO: return {... state,lists:[...state.lists.slice(0,action.index),...state.lists.slice(action.index+1)]} case types.SWITCH_TYPE: console.log({... state,newType:action.newType}) return {... state,newType:action.newType} default: return state; }}Copy the code
In reducer. Js, the initialized state is defined. By using the connect method, we can get the initialized state using this.props. Lists.
import React, { Component } from 'react'; import store from '.. /store'; import actions from '.. /store/actions/list'; import {connect} from 'react-redux'; class Todos extends Component { render(){ return( { + <ul> + this.props.state.lists.map(list =>( + <li>{list.text}</li> + )) + </ul> } <div onCLick={()=>this.props.del_todo() }>test</div> ) } } export default connect( state=>state, actions )(Todos);Copy the code
When state changes, or ownProps changes, mapStateToProps is called, and a new stateProps is computed, which (after merging with ownProps) is updated to MyComp.
2 mapDispatchToProps(dispatch, ownProps): The second parameter to dispatchProps Connect is mapDispatchprops, which binds action as props to MyComp.
action.js
import * as types from ".. /action-types"; export default{ add_todo(text){ return { type: types.ADD_TODO, text: text} }, del_todo(idx){ return {type:types.DEL_TODO, index: idx} }, toggle_todo(index){ return {type:types.TOGGLE_TODO, index} }, del_todo(index){ return {type:types.DEL_TODO, index} }, switch_type(newType){ return {type:types.SWITCH_TYPE, newType} } }Copy the code
The commands I define to change the state in action.js become props bound to the REAC component via connect’s mapDispatchToProps method.
We can easily use to call
<div onCLick={()=>this.props.del_todo() }>test</div>
Copy the code
in-depth
Given this, we can see that we didn’t use the store.dispatch method to issue the command, but the state has changed and the view has changed, so what happened?
store.dispatch(actions.increase())
Copy the code
The key is connect()
Connect Principle simplified version
import React,{Component} from 'react'; import {bindActionCreators} from 'redux'; import propTypes from 'prop-types'; export default function(mapStateToProps,mapDispatchToProps){ return function(WrapedComponent){ class ProxyComponent extends Component{ static contextTypes = { store:propTypes.object } constructor(props,context){ super(props,context); this.store = context.store; this.state = mapStateToProps(this.store.getState()); } componentWillMount(){ this.unsubscribe = this.store.subscribe(()=>{ this.setState(mapStateToProps(this.store.getState())); }); } componentWillUnmount(){ this.unsubscribe(); } render(){ let actions= {}; if(typeof mapDispatchToProps == 'function'){ actions = mapDispatchToProps(this.store.disaptch); }else if(typeof mapDispatchToProps == 'object'){ console.log('object', mapDispatchToProps) actions = bindActionCreators(mapDispatchToProps,this.store.dispatch); } return <WrapedComponent {... this.state} {... actions}/> } } return ProxyComponent; }}Copy the code
1. Return state from connect by returning state from a store on the Provided parent component
mapStateToProps(this.store.getState());
Copy the code
Redux’s helper function, bindActionCreators(), listens on each action with Dispatch.
bindActionCreators(mapDispatchToProps,this.store.dispatch);
Copy the code
To call the method on props, the store.dispach (XXX) event is automatically dispach (XXX)
React-redux simple example project link
reference
Redux is transparent at one o ‘clock
Redux tutorial (3) : React-redux