Redux study notes
Story synopsis
Redux was written by Dan Abramov of Facebook. Redux is a JavaScript state container that provides predictable state management.
Redux is not a must in a project. Instead, you should only use Redux for problems that React really can’t solve.
In general, Redux may be used in the following situations:
- The way users use it is very complex
- Users of different identities have different usage modes (for example, ordinary users and administrators)
- Multiple users can collaborate
- A lot of interaction with the server, or using websockets
- The View gets data from multiple sources
From a component perspective, there are several ways to consider using Redux:
- The state of a component that needs to be shared
- A state needs to be available anywhere
- A component needs to change global state
- One component needs to change the state of another component
The characteristics of story
Single data source
A Single Source of Truth is an entire application that exists in a Single store.
predictability
The only way to change the state is to trigger an action, and you can use the formula state + Action = new State.
Pure functions update Store
To describe how an action updates state, you need to write a Reducer, which must be a pure reducer with output entirely dependent on its input parameters and without any external dependencies or resources inside the function.
Action
The data structure of a behavior described by action, which is the sole source of data for the store.
In general, an action is a normal object in JavaScript that has a type attribute to indicate the action to be performed.
const ADD_TODO = 'ADD_TODO'
const action = {
type: ADD_TODO,
text: 'Build my first Redux app'
}
Copy the code
Action is a notification from the View that state should change.
Reducer
Once the action is received, respond accordingly to update the state.
After the Store receives the Action, it must present a new State before the View changes. This State calculation process is called Reducer.
Reducer is a function that takes Action and the current State as parameters and returns a new State.
There can be multiple Reducer definitions, and each Reducer can receive all the actions.
function todoApp(state = initialState, action) {
switch (action.type) {
case ADD_TODO:
return{... state, ... {stateKey: action.newState } }
default:
return state
}
}
Copy the code
Store
Store consists of three parts: State, Reducer and Dispatcher.
State is the real data; Reducer is the Reducer that handles actions and updates State. The Dispatcher is used to dispatch actions.
Actions will be triggered by some operations on the View, which will be sent to the Dispatcher for distribution. The actions will be updated by the Reducer and the Reducer will update the State, which will be reflected on the View. The whole process is a one-way data flow.
Core API
createStore(reducer)
To create a Store, you need to pass a reducer as a parameter.
import { createStore } from 'redux'
import reducers from './reducers'
const store = createStore(reducers)
Copy the code
getState()
Gets the current State value.
store.getState()
Copy the code
dispatch(action)
To dispatch an action, we need to pass in an action as an argument. When we want to perform some action, we dispatch the corresponding action.
// action creator
function action1() {
return { type: 'ACTION1'.newState: Math.random() }
}
function action3() {
return { type: 'ACTION3'.newState: Math.random() }
}
// store.dispatch({ type: 'ACTION1', newState: Math.random() })
// store.dispatch({ type: 'ACTION3', newState: Math.random() })
store.dispatch(action1())
store.dispatch(action3())
Copy the code
subscribe(listener)
Subscribe to store, and the listener is triggered when state changes.
store.subscribe(() = > console.log(store.getState()))
Copy the code
combineReducers({ reducer1, reducer2, … })
Join multiple reducers together to form a new Reducer.
reducer1.js
export default function reducer1(state = { state1: 1, state2: 'abc' }, action) {
switch (action.type) {
case 'ACTION1':
return{... state, ... {state1: action.newState } }
case 'ACTION2':
return{... state, ... {state2: action.newState } }
default:
return state
}
}
Copy the code
reducer2.js
export default function reducer2(state = { state3: 3, state4: 'def' }, action) {
switch (action.type) {
case 'ACTION3':
return{... state, ... {state3: action.newState } }
case 'ACTION4':
return{... state, ... {state4: action.newState } }
default:
return state
}
}
Copy the code
reducer.js
import { combineReducers } from 'redux'
import reducer1 from './reducer1'
import reducer2 from './reducer2'
export default combineReducers({
reducer1,
reducer2,
})
Copy the code
bindActionCreators
This function is intended to simplify the process used by the Action Creator and Dispatch functions.
import { bindActionCreators, createStore } from 'redux'
// The previous example
// action creator
function action1() {
return { type: 'ACTION1'.newState: Math.random() }
}
store.dispatch(action1())
// The same as the following
function action1() {
return { type: 'ACTION1'.newState: Math.random() }
}
The action argument can be an object and can be passed to more than one Action Creator
const action1Dispatch = bindActionCreators(action1, store.dispatch)
action1Dispatch()
Copy the code
connect
To use Redux in the React component, you must connect Redux to the React component using Connect. Connect is essentially a higher-order function.
connect(mapStateToProps, mapDispatchToProps)(reactComponent): newReactComponent
Pass in the state and Dispatch functions required by this component and return a new React component.
import React from 'react'
import { connect, Provider } from 'react-redux'.class MyComponent1 extends React.Component {
// ...
}
// State in Store is filtered here
// Any state update in the Store will cause a component refresh if not filtered
// This incurs additional performance overhead
function mapStateToProps(state) {
return {
key: state.key, ... }}function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({ actionCreator1, actionCreator2, ... }, dispatch)
}
}
const ConnectedMyComponent1 = connect(mapStateToProps, mapDispatchToProps)(MyComponent1)
class MyComponent1WithStore extends React.Component {
render() {
return (
// Provider components can access the store
<Provider store={store}>
<ConnectedMyComponent1 />
</Provider>)}}Copy the code
Middleware and asynchronous operations
Middleware intercepts some type of action, such as an Ajax request, does a pre-processing, and then forms the final action, issuing the actual action. The process is shown in the figure below:
Middleware usage
Use applyMiddlewares() to assemble all middleware into an array, executed at once.
Take the middleware redux-Logger as an example.
import { applyMiddleware, createStore } from 'redux'
import { createLogger } from 'redux-logger'
const logger = createLogger()
const store = createStore(
reducer,
applyMiddleware(logger)
)
Copy the code
asynchronous
Synchronous operations issue only one Action, and asynchronous operations issue three actions (start, succeed, fail).
The following code snippet introduces asynchronous operations and modifies the Reducer action processing by dispatch actions in asynchronous operations.
function reducers(state = { count: 0, state: 'init' }, action) {
switch (action.type) {
case 'PLUS_START':
return { count: state.count, state: 'pending' }
case 'PLUS_SUCCESS':
return { count: state.count + 2.state: 'plus success' }
case 'PLUS_FAILED':
return { count: state.count, state: 'failed'}...default:
return state
}
}
function plusFailed() {
stop(1000.false).then(res= > {
store.dispatch({ type: 'PLUS_SUCCESS' })
}).catch(e= > {
store.dispatch({ type: 'PLUS_FAILED'})})return { type: 'PLUS_START'}}...Copy the code
Asynchronous middleware
redux-thunk
The above is the basic use of asynchrony, but we need to call Dispatch several times before and after an asynchronous request, which is not very convenient. It would be much easier if there was a way to put an asynchronous operation function directly inside dispatch, but Dispatch could only pass in objects like {type: ‘PLUS_SUCCESS’} as arguments.
To give Dispatch the ability to accept a function as an argument, use the Redux-Thunk middleware.
The introduction method is as follows:
import { applyMiddleware, createStore } from 'redux'
import thunk from 'redux-thunk'
const store = createStore(
reducers,
applyMiddleware(thunk)
)
Copy the code
Usage:
function asyncFunction(param) {
return dispatch= > {
stop(1000, param).then(res= > {
dispatch({ type: 'PLUS_SUCCESS'}}})})// Use method 1
store.dispatch(asyncFunction(param))
// Use method 2
store.dispatch(asyncFunction(param)).then(() = >
console.log(store.getState())
)
Copy the code
redux-promise
This middleware enables Dispatch to receive promise objects.
The introduction method is as follows:
import { createStore, applyMiddleware } from 'redux'
import promiseMiddleware from 'redux-promise'
import reducer from './reducers'
const store = createStore(
reducer,
applyMiddleware(promiseMiddleware)
)
Copy the code
Usage:
// If reject, the action will not be dispatched
function plusPromise() {
return new Promise((resolve, reject) = > {
stop(1000.true).then(() = > {
resolve({ type: 'PLUS_SUCCESS' })
})
})
}
store.dispatch(plusPromise())
Copy the code
Hooks
The Hook version simplifies the use of Redux in components, eliminating the need for mapStateToProps, mapDispatchToProps, Connect, and middleware.
Call action in child component:
- Alternative mapStateToProps useSelector
- UseDispatch replaces mapDispatchToProps and can perform asynchronous operations without using middleware
Introduction method:
import { useSelector, useDispatch } from 'react-redux'
Copy the code
It can be used directly within a function component:
const count = useSelector(state= > {
console.log(state)
return state.count
})
function plus() {
dispatch({ type: 'PLUS_SUCCESS'})}Copy the code
Complete Demo
pure-redux
Reducer1.js, reducer2.js and Reducer.js are examples of combineReducers.
Index. Js as follows:
import React from 'react'
import ReactDOM from 'react-dom'
import { bindActionCreators, createStore } from 'redux'
import reducers from './reducers'
function click() {
const store = createStore(reducers)
function action1() {
return { type: 'ACTION1'.newState: Math.random() }
}
function action3() {
return { type: 'ACTION3'.newState: Math.random() }
}
store.subscribe(() = > console.log(store.getState()))
// store.dispatch({ type: 'ACTION1', newState: Math.random() })
// store.dispatch({ type: 'ACTION3', newState: Math.random() })
const action1Dispatch = bindActionCreators(action1, store.dispatch)
action1Dispatch()
store.dispatch(action3())
}
function Button() {
return (
<button onClick={click}>click me</button>
)
}
ReactDOM.render(
<Button />.document.getElementById('root'))Copy the code
Redux is used in combination with the React component
index.js
import React from 'react'
import ReactDOM from 'react-dom'
import { connect, Provider } from 'react-redux'
import { bindActionCreators, createStore } from 'redux'
function reducers(state = { count: 0 }, action) {
switch (action.type) {
case 'PLUS_ONE':
return { count: state.count + 1 }
case 'MINUS_ONE':
return { count: state.count - 1 }
default:
return state
}
}
const store = createStore(reducers)
function plusOne() {
return { type: 'PLUS_ONE'}}function minusOne() {
return { type: 'MINUS_ONE'}}class MyComponent1 extends React.Component {
render() {
const { count } = this.props
const { minusOne, plusOne } = this.props.actions
return (
<div>
<button onClick={minusOne}>-</button>
{count}
<button onClick={plusOne}>+</button>
</div>)}}function mapStateToProps(state) {
return {
count: state.count,
}
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({ plusOne, minusOne }, dispatch)
}
}
const ConnectedMyComponent1 = connect(mapStateToProps, mapDispatchToProps)(MyComponent1)
class MyComponent1WithStore extends React.Component {
render() {
return (
<Provider store={store}>
<ConnectedMyComponent1 />
</Provider>
)
}
}
ReactDOM.render(
<MyComponent1WithStore />.document.getElementById('root'))Copy the code
The middleware
import React from 'react'
import ReactDOM from 'react-dom'
import { connect, Provider } from 'react-redux'
import { bindActionCreators, createStore, applyMiddleware } from 'redux'
import { createLogger } from 'redux-logger'
const logger = createLogger();
const store = createStore(
reducers,
applyMiddleware(logger)
)
function reducers(state = { count: 0 }, action) {
switch (action.type) {
case 'PLUS_ONE':
return { count: state.count + 1 }
case 'MINUS_ONE':
return { count: state.count - 1 }
default:
return state
}
}
function plusOne() {
return { type: 'PLUS_ONE'}}function minusOne() {
return { type: 'MINUS_ONE'}}class MyComponent1 extends React.Component {
render() {
const { count } = this.props
const { minusOne, plusOne } = this.props.actions
return (
<div>
<button onClick={minusOne}>-</button>
{count}
<button onClick={plusOne}>+</button>
</div>)}}function mapStateToProps(state) {
return {
count: state.count,
}
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({ plusOne, minusOne }, dispatch)
}
}
const ConnectedMyComponent1 = connect(mapStateToProps, mapDispatchToProps)(MyComponent1)
class MyComponent1WithStore extends React.Component {
render() {
return (
<Provider store={store}>
<ConnectedMyComponent1 />
</Provider>
)
}
}
ReactDOM.render(
<MyComponent1WithStore />.document.getElementById('root'))Copy the code
Asynchronous operations
import React from 'react'
import ReactDOM from 'react-dom'
import { connect, Provider } from 'react-redux'
import { bindActionCreators, createStore, applyMiddleware } from 'redux'
import { createLogger } from 'redux-logger'
function stop(ms, isSuccess) {
return new Promise((resolve, reject) = > {
setTimeout(() = > {
if (isSuccess) {
resolve('success')}else {
reject('failed')
}
}, ms)
})
}
const logger = createLogger();
const store = createStore(
reducers,
applyMiddleware(logger)
)
function reducers(state = { count: 0, state: 'init' }, action) {
switch (action.type) {
case 'PLUS_START':
return { count: state.count, state: 'pending' }
case 'PLUS_SUCCESS':
return { count: state.count + 2.state: 'plus success' }
case 'PLUS_FAILED':
return { count: state.count, state: 'failed' }
case 'MINUS_START':
return { count: state.count, state: 'pending' }
case 'MINUS_SUCCESS':
return { count: state.count - 2.state: 'minus success' }
case 'MINUS_FAILED':
return { count: state.count, state: 'failed' }
default:
return state
}
}
function plusFailed() {
stop(1000.false).then(res= > {
store.dispatch({ type: 'PLUS_SUCCESS' })
}).catch(e= > {
store.dispatch({ type: 'PLUS_FAILED'})})return { type: 'PLUS_START'}}function minusFailed() {
stop(1000.false).then(res= > {
store.dispatch({ type: 'MINUS_SUCCESS' })
}).catch(e= > {
store.dispatch({ type: 'MINUS_FAILED'})})return { type: 'MINUS_START'}}function plusSuccess() {
stop(1000.true).then(res= > {
store.dispatch({ type: 'PLUS_SUCCESS' })
}).catch(e= > {
store.dispatch({ type: 'PLUS_FAILED'})})return { type: 'PLUS_START'}}function minusSuccess() {
stop(1000.true).then(res= > {
store.dispatch({ type: 'MINUS_SUCCESS' })
}).catch(e= > {
store.dispatch({ type: 'MINUS_FAILED'})})return { type: 'MINUS_START'}}class MyComponent1 extends React.Component {
render() {
const { count, state } = this.props
const { minusSuccess, plusSuccess, minusFailed, plusFailed } = this.props.actions
return (
<div>
<button onClick={minusSuccess}>Success -</button>
<button onClick={minusFailed}>Failure -</button>
{count}
<button onClick={plusSuccess}>+ successful</button>
<button onClick={plusFailed}>+ failure</button>
<p>
{state}
</p>
</div>)}}function mapStateToProps(state) {
return {
count: state.count,
state: state.state,
}
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({ plusSuccess, minusSuccess, plusFailed, minusFailed }, dispatch)
}
}
const ConnectedMyComponent1 = connect(mapStateToProps, mapDispatchToProps)(MyComponent1)
class MyComponent1WithStore extends React.Component {
render() {
return (
<Provider store={store}>
<ConnectedMyComponent1 />
</Provider>
)
}
}
ReactDOM.render(
<MyComponent1WithStore />.document.getElementById('root'))Copy the code
react-thunk
import React from 'react'
import ReactDOM from 'react-dom'
import { connect, Provider } from 'react-redux'
import { bindActionCreators, createStore, applyMiddleware } from 'redux'
import { createLogger } from 'redux-logger'
import thunk from 'redux-thunk';
function stop(ms, isSuccess) {
return new Promise((resolve, reject) = > {
setTimeout(() = > {
if (isSuccess) {
resolve('success')}else {
reject('failed')
}
}, ms)
})
}
const logger = createLogger();
const store = createStore(
reducers,
applyMiddleware(thunk, logger)
)
function reducers(state = { count: 0, state: 'init' }, action) {
switch (action.type) {
case 'PLUS_SUCCESS':
return { count: state.count + 2.state: 'plus success' }
case 'MINUS_SUCCESS':
return { count: state.count - 2.state: 'minus success' }
default:
return state
}
}
function plusAsync() {
return dispatch= > {
stop(1000.true).then(res= > {
dispatch({ type: 'PLUS_SUCCESS'}}})})function minusAsync() {
return dispatch= > {
stop(1000.true).then(res= > {
dispatch({ type: 'MINUS_SUCCESS'}}})})class MyComponent1 extends React.Component {
render() {
const { count, state } = this.props
const { plusAsync, minusAsync } = this.props.actions
return (
<div>
<button onClick={minusAsync}>-</button>
{count}
<button onClick={plusAsync}>+</button>
<p>
{state}
</p>
</div>)}}function mapStateToProps(state) {
return {
count: state.count,
state: state.state,
}
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({ plusAsync, minusAsync }, dispatch)
}
}
const ConnectedMyComponent1 = connect(mapStateToProps, mapDispatchToProps)(MyComponent1)
class MyComponent1WithStore extends React.Component {
render() {
return (
<Provider store={store}>
<ConnectedMyComponent1 />
</Provider>
)
}
}
ReactDOM.render(
<MyComponent1WithStore />.document.getElementById('root'))Copy the code
react-promise
import React from 'react'
import ReactDOM from 'react-dom'
import { connect, Provider } from 'react-redux'
import { bindActionCreators, createStore, applyMiddleware } from 'redux'
import { createLogger } from 'redux-logger'
import promiseMiddleware from 'redux-promise'
function stop(ms, isSuccess) {
return new Promise((resolve, reject) = > {
setTimeout(() = > {
if (isSuccess) {
resolve('success')}else {
reject('failed')
}
}, ms)
})
}
const logger = createLogger();
const store = createStore(
reducers,
applyMiddleware(promiseMiddleware, logger)
)
function reducers(state = { count: 0, state: 'init' }, action) {
switch (action.type) {
case 'PLUS_SUCCESS':
return { count: state.count + 2.state: 'plus success' }
case 'MINUS_SUCCESS':
return { count: state.count - 2.state: 'minus success' }
default:
return state
}
}
// If reject, the action will not be dispatched
function plusPromise() {
return new Promise((resolve, reject) = > {
stop(1000.true).then(() = > {
resolve({ type: 'PLUS_SUCCESS'})})})}function minusPromise() {
return new Promise((resolve, reject) = > {
stop(1000.true).then(() = > {
resolve({ type: 'MINUS_SUCCESS'})})})}class MyComponent1 extends React.Component {
render() {
const { count, state } = this.props
const { plusPromise, minusPromise } = this.props.actions
return (
<div>
<button onClick={minusPromise}>-</button>
{count}
<button onClick={plusPromise}>+</button>
<p>
{state}
</p>
</div>)}}function mapStateToProps(state) {
return {
count: state.count,
state: state.state,
}
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({ plusPromise, minusPromise }, dispatch)
}
}
const ConnectedMyComponent1 = connect(mapStateToProps, mapDispatchToProps)(MyComponent1)
class MyComponent1WithStore extends React.Component {
render() {
return (
<Provider store={store}>
<ConnectedMyComponent1 />
</Provider>
)
}
}
ReactDOM.render(
<MyComponent1WithStore />.document.getElementById('root'))Copy the code
Hooks
import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import { createStore, applyMiddleware } from 'redux'
import { createLogger } from 'redux-logger'
import { useSelector, useDispatch } from 'react-redux'
function stop(ms, isSuccess) {
return new Promise((resolve, reject) = > {
setTimeout(() = > {
if (isSuccess) {
resolve('success')}else {
reject('failed')
}
}, ms)
})
}
const logger = createLogger();
const store = createStore(
reducers,
applyMiddleware(logger)
)
function reducers(state = { count: 0, state: 'init' }, action) {
switch (action.type) {
case 'PLUS_SUCCESS':
return { count: state.count + 2.state: 'plus success' }
case 'MINUS_SUCCESS':
return { count: state.count - 2.state: 'minus success' }
default:
return state
}
}
function MyComponent1() {
const { count, state } = useSelector(state= > {
console.log(state)
return state
});
const dispatch = useDispatch();
function plus() {
stop(1000.true).then(() = > {
dispatch({ type: 'PLUS_SUCCESS'})})}function minus() {
dispatch({ type: 'MINUS_SUCCESS'})}return (
<div>
<button onClick={minus}>-</button>
{count}
<button onClick={plus}>+</button>
<p>
{state}
</p>
</div>
)
}
ReactDOM.render(
<Provider store={store}>
<MyComponent1 />
</Provider>.document.getElementById('root'))Copy the code