在之前的代码中,必须将网络请求的异步代码放到组件的生命周期中来完成。
// increase.js
import React, {PureComponent} from 'react'
import {connect} from 'react-redux'
import axios from 'axios'
import {increaseAction} from '../store/action'
class Increase extends PureComponent {
handleIncrease = () => {
// 网络请求的异步代码
axios.get('http://123.207.32.32:8000/home/multidata').then(res => {
const count = res.data.data.banner.list.length
this.props.handleCountIncrease(count)
})
}
render() {
return (
<div>
<button onClick={this.handleIncrease}>增加</button>
</div>
)
}
}
const mapDispatchToProps = dispatch => ({
handleCountIncrease: count => {
dispatch(increaseAction(count))
}
})
export default connect(null, mapDispatchToProps)(Increase)
但实际上,网络请求到的数据也属于状态管理的一部分,更好的做法是将它们也交给 Redux 来管理。
由于 Action 是一个对象,所以无法在 Action 中进行网络请求; Reducer 是一个纯函数,不能有副作用,所以也不能在 Reducer 中进行网络请求。因此只能尝试对 dispatch()
进行处理。
Redux 中间件:
Redux 引入了中间件 Middleware 的概念。
Redux 中间件的核心是在 dispatch 之后,到达 Reducer 之前进行一些扩展。
实现原理是在 dispatch 派发 action 的时候对其进行拦截,通过 Monkey Patching 的方式对原本的 dispatch 进行修改,让其指向一个新函数,在新函数中执行需要的逻辑后再使用原本的 dispatch 真正地派发 action。其实就是对 Redux 的 dispatch 进行了一层封装,增强了 dispatch 的能力。
Monkey Patching 猴补丁:通过篡改现有的代码,对程序逻辑进行修改。
可以利用 Redux 中间件来进行日志记录(redux-logger
)、执行异步操作(redux-thunk
)等。
没有中间件:
store.dispatch()
就是 Redux 自己的 dispatch 方法,用来发起状态更新。dispatch() => reducer。
使用中间件:store.dispatch()
就是中间件封装后的 dispatch,但是最终还是一定会调用 Redux 自己的 dispatch 方法发起状态更新。dispatch() => 执行中间件代码 => reducer。
在 Redux 中注册中间件。
// 以 redux-thunk 为例
// 1. 从 redux 中引入 applyMiddleware 方法,以逗号分割可传入多个中间件
import {createStore, applyMiddleware} from 'redux'
// 2. 从 react-thunk 中引入中间件 thunk
import thunk from 'redux-thunk'
import reducer from './reducer'
// 3. createStore() 可传入第二个参数来注册中间件,对 dispatch 进行增强
export default createStore(reducer, applyMiddleware(thunk))
手动实现中间件:
实现一个日志打印的中间件:在派发 action 之前打印 action,在派发 action 之后打印 state。
// store/index.js
import {createStore} from 'redux'
import reducer from './features/count'
const store = createStore(reducer)
function log(store) {
// 1. 保存真正的 dispatch
const next = store.dispatch
function logAndDispatch(action) {
console.log('当前派发的 action', action)
// 3. 使用真正的 dispatch 派发 action
next(action)
console.log('派发之后的结果', store.getState())
}
// 2. 修改 dispatch 的指向。之后通过 dispatch 派发 action 的时候,执行的将会是 logAndDispatch
store.dispatch = logAndDispatch
}
log(store)
export default store
实现一个异步操作的中间件。主要就是可以让 dispatch 派发一个函数,就可以在这个函数内部做任何事情了
// store/index.js
import {createStore} from 'redux'
import reducer from './features/count'
const store = createStore(reducer)
function thunk(store) {
// 1. 保存真正的 dispatch
const next = store.dispatch
function dispatchThunk(action) {
// 如果判断到 action 是一个函数,则执行这个函数;否则使用真正的 dispatch 派发 action
if (typeof action === 'function') {
action(next, store.getState)
} else {
next(action)
}
}
// 2. 修改 dispatch 的指向。之后通过 dispatch 派发 action 的时候,执行的将会是 dispatchThunk
store.dispatch = dispatchThunk
}
thunk(store)
export default store
redux-thunk
中间件:
redux-thunk
的主要功能就是可以让 dispatch 派发一个函数,那么,就可以在这个函数内部做任何事情了(例如:进行网络请求等)。因此,通常使用 redux-thunk
中间件来处理异步操作。
Redux 中的 dispatch 正常情况下只能派发一个对象。
redux-thunk
的核心代码其实就是判断每个经过它的 action:如果是 function 类型,就调用这个 function,而不是任由让它到达 reducer。因为 reducer 是个纯函数,Redux 规定到达 reducer 的 action 必须是一个 plain object 类型。
安装:
npm install redux-thunk
使用:
- 注册
redux-thunk
中间件来对 dispatch 进行增强。// store/index.js // 从 redux 中引入 applyMiddleware 方法,以逗号分割可传入多个中间件 import {createStore, applyMiddleware} from 'redux' // 从 react-thunk 中引入中间件 thunk import thunk from 'redux-thunk' import reducer from './reducer' // createStore() 可传入第二个参数来注册中间件,对 dispatch 进行增强 export default createStore(reducer, applyMiddleware(thunk))
- 在组件中,通过
store.dispatch()
派发的时候派发一个函数,该函数会被自动执行,并传入 dispatch 和 getState 两个方法作为其参数,在函数内部执行完异步操作之后,再通过 dispatch 参数派发一个 Action 对象。也就是说,开始派发的函数的作用只是可以在其内部进行异步操作,最终派发出去的仍然是一个 Action 对象,Reducer 会在接收到该 Action 后返回最新的 State。
// increase.js import React, {PureComponent} from 'react' import {connect} from 'react-redux' import {increaseAction} from '../store/action' class Increase extends PureComponent { handleIncrease = () => { this.props.handleCountDecrease() } render() { return ( <div> <button onClick={this.handleIncrease}>增加</button> </div> ) } } const mapDispatchToProps = dispatch => ({ handleCountDecrease: () => { // store.dispatch() 派发一个函数,派发的函数会被自动执行 dispatch(increaseAction()) } }) export default connect(null, mapDispatchToProps)(Increase)
// store/action.js import axios from 'axios' // 创建 Action 的 Action Creator export const increaseAction = () => { // 派发的函数会接收 Store 中的 dispatch 和 getState 两个方法作为参数 return (dispatch, getState) => { axios.get('http://123.207.32.32:8000/home/multidata').then(res => { const count = res.data.data.banner.list.length // 在派发的函数内部执行完异步操作之后,再通过 dispatch 派发一个 Action 对象 dispatch({ type: 'increase', count, }) }) } }