Redux详解(二)之Redux 中间件

160 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第10天,点击查看活动详情 

中间件middleware

本质上就是一个柯里化的函数,reducer之前运行的函数。

graph LR
Component--dispatch--->Action --> Store --action发给中间件--> middleware 
Store--状态同步到组件-->Component
Reducer--将状态返回-->Store
middleware--处理完后发给reducer--> Reducer

开发中间件

1. 开发中间件

中间件模板代码export default store => next => action => {逻辑代码} 调用next(action)后将后续操作交给reducer

// logger.js

export default store => next => action => {
  // 这里面处理我们要加的代码逻辑,我们在这里加上打印注释
  console.log(action)
  next(action);
}

2. 注册中间件

import {createStore, applyMiddleware} from 'redux'
import logger from './middlewares/logger'
const store = createStore(reducer, applyMiddleware(logger));

处理耗时任务的中间件

问题:耗时操作的action我们要怎么做?

应用场景: 按钮点击-->请求数据-->保存数据-->更新视图 请求数据耗时操作。

  • View页面,等接口请求完成后,dispatch再更新UI(方案1)
  • 可否在reducer里面执行耗时操作后再return(方案2)
  • redux支持异步action(方案3)

方案1

存在一些问题,首先所有请求都放在view页面,导致view页面臃肿,mvvm思想要求所有与UI无关的数据操作全部放在store里面。,其次多个页面共用同一套逻辑的情况同样会导致view臃肿。所以不建议第一种

方案2

如下代码


const reducer =  (state = initialState, action) => {
    switch (action.type){
        case 'increment_async':
          setTimeout(() => {
               return {
                   ...initialState,
                   count: state.count - 5
               }
           }, 2000)
            break

        default:
            return state

    }
}

我们会发现redux内部执行了

// 断点发现`previousStateForKey= {count: 0}`   action = {type: "increment_async"}
var nextStateForKey = reducer(previousStateForKey, action);

由于reducer要两秒后才返回 所以会导致nextStateForKey 返回undefined 于是抛出错误 Uncaught Error: When called with an action of type "increment_async", the slice reducer for key "counter" returned undefined. To ignore an action, you must explicitly return the previous state. If you want this reducer to hold no value, you can return null instead of undefined.

方案3

最终发现只有方案三最优解。

中间件thunk

thunk很容易,要求我们发送的action为方法。thunk会判断如果是方法,则会认为改操作为异步action(即穿进去的方法未异步的) 如果同步则为对象,当调用传进来的action方法时候需要传入dispatch

自定义thunk

//thunk.js

1 export default ({dispatch}) => next => action => {
2     if (typeof action === 'function') {
3         return action(dispatch)
4     }
5   next(action)
6  }

// counter.js

const mapDispatchToProps = dispatch => bindActionCreators({
    increment5: payload => ({
        type: 'increment5',
    }),
    increment_async: payload => dispatch => {
        setTimeout(() => {
            dispatch({type: 'increment5', payload})
        }, 2000)
    }
}, dispatch)

这里要好好理解一下 thunk 中的第三行代码调用action方法传递dispatch参数 实际上就是调用下面的代码,下面代码就相当于等待两秒执行同步的action,相当于通过中间件,将action转化为异步函数,执行同步action

1  dispatch => {
2        setTimeout(() => {
3            dispatch({type: 'increment5', payload})
4        }, 2000)
5    }

redux-thunk

redux-thunk 和上述自定义thunk完全一致,使用方法也是一致

redux-saga

redux-sagathunk作用完全一致,也是处理异步action的中间件,redux-saga跟完备,更强大

redux-saga的使用

1. 注册saga中间件
import createSagaMiddleware from 'redux-saga'
import counterSaga from "./saga/counter.saga";

// 1. 创建saga中间件变量
const sagaMiddleware = createSagaMiddleware();

// 2. 在createStore 中注册

const store = createStore(rootReducer, applyMiddleware(logger, sagaMiddleware))

// 第三步 用sagaMiddleware.run(具体sage实例)
sagaMiddleware.run(counterSaga)
2. 使用saga中间件
  • 定义异步action :increment_asyncconnect的第二个参数mapDispatchToProps中 新建存放action类counter.actions.js文件
export const increment = payload => ({
    type: 'increment', payload:{tick: payload}
})
export const decrement5 = payload => ({
    type: 'decrement5',
})
export const increment5 =  payload => ({
    type: 'increment5',
})

export const increment_async = payload => ({
    type: 'increment_async',
    payload
})


import * as counterActions from '../store/actions/counter.actions';
const mapDispatchToProps = dispatch => bindActionCreators(counterActions, dispatch)
3. 定义counter.saga.js文件

1 import { takeEvery, put, delay } from 'redux-saga/effects';
2 import { increment } from '../actions/counter.actions';

3 function* increment_async_fn (action) {
4   yield delay(2000);
5   yield put(increment(action.payload))
6 }

7 export default function* counterSaga () {
8   yield takeEvery('increment_async', increment_async_fn)
9 }
  1. takeEvery 用来获取action,实际相当于把type和实际执行的方法绑定,在注册saga中间件的时候将这种映射关系注册进去。

  2. put相当于dispatch 第5行代码实际相当于执行

// const increment = payload => ({type: 'increment', payload:{tick:payload}})
yield dispatch{type: 'increment', payload:{tick: action.payload}}

action 是调用increment_asyncaction 其中typeincrement_asyncaction.payload 是调用increment_async这个action传递的参数

  1. delay: saga里面的定时器。我们在这里用来模拟接口请求

  2. 实际接口请求用到的是call关键字 用来执行异步函数

  3. select 用于从state中获取已保存的参数

// 从modal里面获取以保存的参数isEdit,来判断当前页面是否处于编辑状态
const { isEdit } = yield select(({modal}) => modal)
// call调用接口,通过isEdit判断如果处于编辑状态调用编辑接口editUrl,否则调用添加接口addUrl
const { data, code } = yield call(POST, isEdit ?api.editUrl : api.addUrl, params);

redux-saga的优化

随着项目增加,sagaMiddleware.run(counterSaga)将存在很多类似于counterSaga的文件,此时我们可以用all方法来合并所有saga

// 详见root.saga.js 文件

import { all } from 'redux-saga/effects';
import counterSaga from './counter.saga';
import modalSaga from './modal.saga';

export default function* rootSaga () {
  yield all([
    counterSaga(), // 执行counterSaga
    modalSaga()  // 执行modalSaga
  ])
}

redux-action 简化代码

redux-action用来简化action和reducer的。使用该中间件后,将最大限度简化action和reducer的定义。省去很多重复代码。使用起来清爽很多 redux-action官方文档

关键常用的两个方法

createAction: 用来创建action的

handleActions: 用来创建reducer的

使用方法

1. 改造action

原来的action

export const increment = payload => ({
    type: 'increment', payload:{tick: payload}
})
export const decrement5 = payload => ({
    type: 'decrement5',
})
export const increment5 =  payload => ({
    type: 'increment5',
})

export const increment_async = payload => ({
    type: 'increment_async',
    payload
})

改造后的action

import { createAction }  from 'redux-actions';
export const increment = createAction('increment')
export const decrement5 = createAction('decrement5')
export const increment5 = createAction('increment5')

2.改造reducer

import { handleActions }  from 'redux-actions';

export default handleActions(
    {
        [increment]: (state, action) => ({ ...state, counter: state.counter + action.payload }),
        [decrement5]: (state) => ({ ...state, counter: state.counter - 5 }),
        [increment5]: (state) => ({ ...state, counter: state.counter + 5 }),
    },
    initialState
);

其他redux-actionsapi

其他几个action用的比较少

createActions,createCurriedAction,handleAction总体来说带s的则是对象{ action: function }形式

combineActions 不同的action 执行相同的方法

createActions 所有action 以参数形式传递

上面代码可以简述为

export default createActions('increment','increment5','decrement5')

handleAction 每个action的reducer单独处理

const inscrementReducer = handleAction(increment, (state, action) => ({ ...state, counter: state.counter + action.payload }), defaultState)

总结

thunksaga来实现耗时操作, redux-action简化繁琐的重复代码,让新手更易上手