中间件本质上就是一个函数。
中间件允许我们扩展和增强 redux 应用程序,主要体现在对 Action 的处理能力上。
一般情况下,一个 Action 的会直接被 Reducer 处理的,当加入了中间件之后,这个 Action 会优先被中间件处理,当中间件处理完毕后,再将这个 Action 传递给 Reducer 继续处理。
加入了中间件 Redux 工作流程:
Redux 中间件本质上是一个柯里化形式的函数。
开发中间件的模板代码:
export default store => next => action => {
}
中间件函数返回一个函数,这个函数再返回一个函数,在最里层的函数中执行自己的业务逻辑。
中间件在开发完成后只有被注册才能在 Redux 的工作流程中生效。
使用 applyMiddleware
注册中间件
import {
createStore, applyMiddleware } from 'redux'
import logger from './middlewares/logger'
createStore(reducer, applyMiddleware(
logger
))
中间件的执行顺序,取决于注册时的顺序。
示例中间件功能:当组件触发一个功能的时候,打印一些信息到控制台中。
// src\store\middlewares\logger.js
const logger = store => next => action => {
console.log(store)
console.log(action)
next(action)
}
export default logger
// src\store\middlewares\test.js
const test = store => next => action => {
console.log('test 中间件执行')
next(action)
}
export default test
// src\store\index.js
import {
createStore, applyMiddleware } from 'redux'
import RootReducer from './reducers/root.reducer'
import logger from './middlewares/logger'
import test from './middlewares/test'
export const store = createStore(RootReducer, applyMiddleware(
logger,
test
))
// 先执行 logger 再执行 test
// src\store\middlewares\thunk.js
import {
DECREMENT, INCREMENT } from '../const/counter.const'
const thunk = ({
dispatch }) => next => action => {
if (action.type === DECREMENT || action.type === INCREMENT) {
setTimeout(() => {
next(action)
}, 1000)
} else {
next(action)
}
}
export default thunk
// src\store\index.js
import {
createStore, applyMiddleware } from 'redux'
import RootReducer from './reducers/root.reducer'
import logger from './middlewares/logger'
import test from './middlewares/test'
import thunk from './middlewares/thunk'
export const store = createStore(RootReducer, applyMiddleware(
logger,
test,
thunk
))
现在使用中间件实现了延迟(异步)计时的功能。
但是要为其它 Action 添加异步功能时,就要扩展这个中间件。
为了避免逻辑集中在中间件中,可以这样实现:
新建一个函数类型的 Action,表示异步操作,当异步内容执行完毕,调用 dispatch 触发另一个同步操作,并将参数传递给它:
// src\store\actions\counter.action.js
import {
DECREMENT, INCREMENT } from "../const/counter.const"
export const increment = payload => ({
type: INCREMENT, payload })
export const decrement = payload => ({
type: DECREMENT, payload })
export const increment_async = payload => dispatch => {
setTimeout(() => {
dispatch(increment(payload))
}, 1000)
}
修改中间件判断异步操作:
// src\store\middlewares\thunk.js
const thunk = ({
dispatch }) => next => action => {
if (typeof action === 'function') {
return action(dispatch)
}
next(action)
}
export default thunk
修改组件执行异步操作:
// src/components/Counter.js
function Counter({ count, decrement, increment_async }) {
return (
{count}
)
}
这样增加其它的异步操作,只需要新建一个函数类的 Action 即可,而不需要扩展中间件的内容。
redux-thunk 可以在 redux 的工作流程当中加入异步操作。
上面的 thunk 中间件就是仿造的 redux-thunk 中间件。
下载:
npm install redux-thunk
引入:
import thunk from 'redux-thunk'
注册:
import {
applyMiddleware } from 'redux'
createStore(rootReducer, applyMiddleware(thunk))
使用:
const loadPosts = () => async dispatch => {
const posts = await axios.get('/api/posts').then(response => response.data)
dispatch({
type: LOADPOSTSSUCCESS, payload: posts})
}
替换仿造的实例:
// src\store\index.js
import {
createStore, applyMiddleware } from 'redux'
import RootReducer from './reducers/root.reducer'
import logger from './middlewares/logger'
import test from './middlewares/test'
// import thunk from './middlewares/thunk'
import thunk from 'redux-thunk'
export const store = createStore(RootReducer, applyMiddleware(
logger,
test,
thunk
))
redux-saga 和 redux-thunk 的作用一样,都是往 redux 的工作流程当中加入异步代码。
redux-saga 更为强大,它可以将异步操作的代码从 ActionCreator 文件中抽离出来,放在一个单独的文件中,使得项目代码更加可维护。
下载:
npm install redux-saga
创建 redux-saga 中间件:
import createSagaMiddleware from 'redux-saga'
const sagaMiddleware = createSagaMiddleware()
注册:
createStore(reducer, applyMiddleware(sagaMiddleware))
创建 Saga 接收 Action 执行异步操作:
import {
takeEvery, put } from 'redux-saga/effects'
function* load_posts () {
const {
data } = yield axios.get('/api/posts.json')
yield put(load_posts_success(data))
}
export default function* postSaga () {
yield takeEvery(LOAD_POSTS, load_posts)
}
takeEvery(pattern, saga, ...args)
参数:
pattern: String | Array | Function
- 如果是字符串,就是要接受的 Action 的 typesaga: Function
- 接收到 Action 后要执行的 Generator 函数args: Array
- 传递给 saga 函数的参数,takeEvery 会把当前 action 追加到参数列表中(最后一个参数)启用 Saga(加入到 Redux 工作流程中):
import postSaga from './store/sagas/post.saga'
sagaMiddleware.run(postSaga)
添加异步操作的常量
// src\store\const\counter.const.js
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
export const INCREMENT_ASYNC = 'increment_async'
添加一个异步操作的 Action(注意组件中调用对应的触发方法)
// src\store\actions\counter.action.js
import {
DECREMENT, INCREMENT, INCREMENT_ASYNC } from "../const/counter.const"
export const increment = payload => ({
type: INCREMENT, payload })
export const decrement = payload => ({
type: DECREMENT, payload })
export const increment_async = payload => ({
type: INCREMENT_ASYNC, payload })
创建 Saga
// src\store\sagas\counter.saga.js
import {
takeEvery, put, delay } from 'redux-saga/effects'
import {
increment } from '../actions/counter.action'
import {
INCREMENT_ASYNC } from '../const/counter.const'
// takeEvery 接收 action
// put 触发 action
// delay 用于延迟
function * increment_async_fn (action) {
yield delay(1000)
yield put(increment(action.payload))
}
export default function * counterSaga() {
// 接收 action
yield takeEvery(INCREMENT_ASYNC, increment_async_fn)
}
引入、注册 sagaMiddleware,启用 saga:
// src\store\index.js
import {
createStore, applyMiddleware } from 'redux'
import RootReducer from './reducers/root.reducer'
import logger from './middlewares/logger'
import test from './middlewares/test'
// import thunk from './middlewares/thunk'
// import thunk from 'redux-thunk'
import createSagaMiddleware from 'redux-saga'
import counterSaga from './sagas/counter.saga'
// 创建 sagaMiddleware
const sagaMiddleware = createSagaMiddleware()
export const store = createStore(RootReducer, applyMiddleware(
logger,
test,
sagaMiddleware
))
// 启动 counterSaga
sagaMiddleware.run(counterSaga)
先在 counter.saga 中添加弹窗的异步操作。
添加 type 常量:
// src\store\const\modal.const.js
export const SHOWMODAL = 'showModal'
export const HIDEMODAL = 'hideModal'
export const SHOWMODAL_ASYNC = 'showModal_async'
添加异步操作的 Action(注意组件中调用对应的触发方法):
// src\store\actions\modal.actions.js
import {
HIDEMODAL, SHOWMODAL, SHOWMODAL_ASYNC } from "../const/modal.const"
export const show = () => ({
type: SHOWMODAL })
export const hide = () => ({
type: HIDEMODAL })
// export const show_async = () => dispatch => {
// setTimeout(() => {
// dispatch(show())
// }, 1000);
// }
export const show_async = () => ({
type: SHOWMODAL_ASYNC })
在 Saga 中添加异步操作:
// src\store\sagas\counter.saga.js
import {
takeEvery, put, delay } from 'redux-saga/effects'
import {
increment } from '../actions/counter.action'
import {
show } from '../actions/modal.actions'
import {
INCREMENT_ASYNC } from '../const/counter.const'
import {
SHOWMODAL_ASYNC } from '../const/modal.const'
// takeEvery 接收 action
// put 触发 action
// delay 用于延迟
function * increment_async_fn (action) {
yield delay(1000)
yield put(increment(action.payload))
}
function * showModal_async_fn () {
yield delay(1000)
yield put(show())
}
export default function * counterSaga() {
// 接收 action
yield takeEvery(INCREMENT_ASYNC, increment_async_fn)
yield takeEvery(SHOWMODAL_ASYNC, showModal_async_fn)
}
如果在当前 Saga 文件中接收所有的异步 Action,拿它就会变得臃肿,所以需要拆分,将一个大的 Sage 拆分成多个小的 Saga,然后合并成一个 Saga,类似拆分合并 reducers。
新建一个 modal.saga
// src\store\sagas\modal.saga.js
import {
takeEvery, put, delay } from 'redux-saga/effects'
import {
show } from '../actions/modal.actions'
import {
SHOWMODAL_ASYNC } from '../const/modal.const'
function * showModal_async_fn () {
yield delay(1000)
yield put(show())
}
export default function * counterSaga() {
// 接收 action
yield takeEvery(SHOWMODAL_ASYNC, showModal_async_fn)
}
删除 counter.saga 中的 modal 异步操作:
// src\store\sagas\counter.saga.js
import {
takeEvery, put, delay } from 'redux-saga/effects'
import {
increment } from '../actions/counter.action'
import {
INCREMENT_ASYNC } from '../const/counter.const'
// takeEvery 接收 action
// put 触发 action
// delay 用于延迟
function * increment_async_fn (action) {
yield delay(1000)
yield put(increment(action.payload))
}
export default function * counterSaga() {
// 接收 action
yield takeEvery(INCREMENT_ASYNC, increment_async_fn)
}
使用 all
方法合并 sagas:
all
方法接收 saga 的调用// src\store\sagas\root.saga.js
import {
all } from 'redux-saga/effects'
import counterSaga from './counter.saga'
import modalSaga from './modal.saga'
export default function * rootSage () {
yield all([
counterSaga(),
modalSaga()
])
}
使用合并后的 saga:
// src\store\index.js
import {
createStore, applyMiddleware } from 'redux'
import RootReducer from './reducers/root.reducer'
// import logger from './middlewares/logger'
// import test from './middlewares/test'
// import thunk from './middlewares/thunk'
// import thunk from 'redux-thunk'
import createSagaMiddleware from 'redux-saga'
// import counterSaga from './sagas/counter.saga'
// import modalSaga from './sagas/modal.saga'
import rootSage from './sagas/root.saga'
// 创建 sagaMiddleware
const sagaMiddleware = createSagaMiddleware()
export const store = createStore(RootReducer, applyMiddleware(
// logger,
// test,
sagaMiddleware
))
// 启动 counterSaga
sagaMiddleware.run(rootSage)
redux 流程中大量的样板代码读写很痛苦,使用 redux-actions 可以简化 Action 和 Reducer 的代码。
样板代码:
switch case
匹配处理 Acitonnpm install redux-actions
createAction
方法可以生成 ActionCreator 函数,它接收一个字符串作为 Action 对象的 type。
简化了创建 ActionCreator 和定义常量的代码。
import {
createAction } from 'redux-actions'
const increment_action = createAction('increment')
const decrement_action = createAction('decrement')
handleActions
用于接收处理 Actions,它调用的返回值就是 Reducer 函数。
handleActions(reducerMap, defaultState[, options])
:
reducerMap
- 一个对象
import {
handleActions as createReducer } from 'redux-actions'
import {
increment_action, decrement_action } from '../actions/counter.action'
const initialState = {
count: 0 }
const counterReducer = createReducer({
[increment_action]: (state, action) => ({
count: state.count + 1 }),
[decrement_action]: (state, action) => ({
count: state.count - 1 })
}, initialState)
export default counterReducer
// src\store\actions\counter.action.js
import {
createAction } from 'redux-actions'
export const increment = createAction('increment')
export const decrement = createAction('decrement')
// 异步操作
export const increment_async = createAction('increment_async')
// src\store\reducers\counter.reducer.js
import {
handleActions as createReducer } from 'redux-actions'
import {
increment, decrement } from '../actions/counter.action'
const initialState = {
count: 0,
}
const handleIncrement = (state, aciton) => {
return {
count: state.count + aciton.payload
}
}
const handleDecrement = (state, aciton) => {
return {
count: state.count - aciton.payload
}
}
export default createReducer({
[increment]: handleIncrement,
[decrement]: handleDecrement
}, initialState)
// src\store\sagas\counter.saga.js
import {
takeEvery, put, delay } from 'redux-saga/effects'
import {
increment, increment_async } from '../actions/counter.action'
// import { INCREMENT_ASYNC } from '../const/counter.const'
// takeEvery 接收 action
// put 触发 action
// delay 用于延迟
function * increment_async_fn (action) {
yield delay(1000)
yield put(increment(action.payload))
}
export default function * counterSaga() {
// 接收 action
// takeEvery 第一个参数接收 type 字符串
// yield takeEvery(INCREMENT_ASYNC, increment_async_fn)
// 或者
// 接收 redux-actions 创建的 actionCreator
yield takeEvery(increment_async, increment_async_fn)
}
// src/components/Counter.js
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import * as counterActions from '../store/actions/counter.action'
function Counter({ count, increment_async, decrement }) {
return (
{count}
)
}
const mapStateToProps = state => ({
count: state.counter.count
})
const mapDispatchToProps = dispatch => bindActionCreators(counterActions, dispatch)
export default connect(mapStateToProps, mapDispatchToProps)(Counter)