Redux学习 - 03 中间件

Redux 中间件 middleware

什么是中间件

中间件本质上就是一个函数。

中间件允许我们扩展和增强 redux 应用程序,主要体现在对 Action 的处理能力上。

一般情况下,一个 Action 的会直接被 Reducer 处理的,当加入了中间件之后,这个 Action 会优先被中间件处理,当中间件处理完毕后,再将这个 Action 传递给 Reducer 继续处理。

加入了中间件 Redux 工作流程:

在这里插入图片描述

开发中间件

Redux 中间件本质上是一个柯里化形式的函数。

开发中间件的模板代码:

export default store => next => action => {}

中间件函数返回一个函数,这个函数再返回一个函数,在最里层的函数中执行自己的业务逻辑。

  • store - 可以调用 getState dispatch 等方法
  • action - 要处理的 Action
  • next - 一个函数
    • 当中间件的逻辑执行完成后,要调用 next 方法,才能将当前 Action 传递给 Reducer 或下一个中间件

注册中间件

中间件在开发完成后只有被注册才能在 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 添加异步功能时,就要扩展这个中间件。

为了避免逻辑集中在中间件中,可以这样实现:

  1. 当前中间件函数不需要关心要执行的异步操作的内容,只关心执行的是不是异步操作
    1. ActionCreator 函数(创建 Action 的方法)可以返回一个 Action 对象,也可以返回一个函数
    2. 指定当 Action 是一个函数的时候,就当作它是异步操作,异步操作的内容就写在这个函数中
  2. 如果执行的是同步操作,就传递 Action 对象
  3. 如果执行的是异步操作,就调用这个函数,并将 dispatch 方法传递给它,还要中断处理过程,避免后面接着处理。
  4. 当异步操作执行完毕后,通过调用 dispatch 去触发另一个 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 (
    <div>
      <button onClick={() => increment_async(5)}>+</button>
      <span>{count}</span>
      <button onClick={() => decrement(5)}>-</button>
    </div>
  )
}

这样增加其它的异步操作,只需要新建一个函数类的 Action 即可,而不需要扩展中间件的内容。

Redux 常用中间件

redux-thunk

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-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 - 用于接收 Action
    • 当组件触发一个 Action 时,在 saga 文件中,可以通过 takeEvery 方法接收这个 Action
    • takeEvery(pattern, saga, ...args) 参数:
      • pattern: String | Array | Function - 如果是字符串,就是要接受的 Action 的 type
      • saga: Function - 接收到 Action 后要执行的 Generator 函数
      • args: Array<any> - 传递给 saga 函数的参数,takeEvery 会把当前 action 追加到参数列表中(最后一个参数)
  • put - 用于触发另一个 Action
    • 当异步操作返回结果后,需要通过 put 方法触发一个 Action,把异步操作的结果传递给 reducer,从而将状态存储在 store 中。
    • put 的作用和 dispatch 一样
  • saga 文件要求默认导出一个 Generator 函数

启用 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)

saga 文件的拆分与合并

扩展 Saga

先在 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:

  • 返回一个 Generator 函数
  • 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-actions

reudx-actions 解决的问题

redux 流程中大量的样板代码读写很痛苦,使用 redux-actions 可以简化 Action 和 Reducer 的代码。

样板代码:

  • ActionCreator 文件中大量的 ActionCreator 创建函数
  • Action 对象中的 type 字符串抽象成常量放到单独的文件中
  • Reducer 中使用 switch case 匹配处理 Aciton

使用

下载
npm install redux-actions
创建 Action

createAction 方法可以生成 ActionCreator 函数,它接收一个字符串作为 Action 对象的 type。

简化了创建 ActionCreator 和定义常量的代码。

import { createAction } from 'redux-actions'

const increment_action = createAction('increment')
const decrement_action = createAction('decrement')
创建 Reducer

handleActions 用于接收处理 Actions,它调用的返回值就是 Reducer 函数。

handleActions(reducerMap, defaultState[, options])

  • reducerMap - 一个对象
    • key - 可以是 createAction 创建的 ActionCreator 函数
    • value - 处理函数,返回的对象就是要存储到 store 中的数据
  • defaultState - 初始状态
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 (
    <div>
      <button onClick={() => increment_async(5)}>+</button>
      <span>{count}</span>
      <button onClick={() => decrement(5)}>-</button>
    </div>
  )
}

const mapStateToProps = state => ({
  count: state.counter.count
})

const mapDispatchToProps = dispatch => bindActionCreators(counterActions, dispatch)

export default connect(mapStateToProps, mapDispatchToProps)(Counter)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值