redux功能强大的Middleware中间件使用学习

目录
  • 引言
  • redux中的Middleware
  • 记录日志
    • 手动记录
  • redux-saga
    • Generator函数
    • 实际使用场景

引言

上一节我们学习了redux在实际项目的应用细节,这一节我们来学习redux中一个很重要的概念:中间件。我们会简单实现一个记录的中间件, 然后学习redux-saga这个异步请求中间件。

redux中的Middleware

redux中的中间件提供的是位于 action 被发起之后,到达 reducer 之前的扩展点。 你可以利用 Redux middleware 来进行日志记录、创建崩溃报告、调用异步接口或者路由等等。

记录日志

试想一下,如果我们的redux在每一次dispatch的时候都可以记录下此次发生的action以及dispatch结束后的store。那么在我们的应用 出现问题的时候,我们就可以轻松的查阅日志找出是哪个action导致了state不正确。那么我们怎样通过redux实现它呢?

手动记录

最直接的解决方案就是在每次调用 store.dispatch(action) 前后手动记录被发起的 action 和新的 state。假如你在创建一个action时这样调用:

store.dispatch(addTodo('use Redux'))

为了记录这个 action 以及产生的新的 state,你可以通过这种方式记录日志:

let action = addTodo('Use Redux')
console.log('dispatching', action)
store.dispatch(action)
console.log('next state', store.getState())

那么很自然的就能想到可以封装为一个函数在各处调用:

function dispatchAndLog(store, action) {
  console.log('dispatching', action)
  store.dispatch(action)
  console.log('next state', store.getState())
}
dispatchAndLog(store, addTodo('Use Redux'))

但是这样我们还是需要每次导入一个外部方法,那么如果我们直接去替换store实例中的dispatch函数呢?

let next = store.dispatch
store.dispatch = function dispatchAndLog(action) {
  console.log('dispatching', action)
  let result = next(action)
  console.log('next state', store.getState())
  return result
}

其实到这里我们想要实现的功能已经完成了,但是距离Middleware实际使用的方法还是有不小的差距, 同时我们这里只能对dispatch的扩展时十分有限的,如果我想对其添加其他的功能,又该怎么实现呢? 首先可以确定的是我们需要将每一个功能分离开来,我们希望的时一个功能对应一个模块,那么当我们想添加其他的模块时,应该是这样的:

function patchStoreToAddLogging(store) {
  let next = store.dispatch
  store.dispatch = function dispatchAndLog(action) {
    console.log('dispatching', action)
    let result = next(action)
    console.log('next state', store.getState())
    return result
  }
}
// 崩溃报告模块
function patchStoreToAddCrashReporting(store) {
  let next = store.dispatch
  store.dispatch = function dispatchAndReportErrors(action) {
    try {
      return next(action)
    } catch (err) {
      console.error('捕获一个异常!', err)
      Raven.captureException(err, {
        extra: {
          action,
          state: store.getState()
        }
      })
      throw err
    }
  }
}

然后我们可以在store中使用它们:

patchStoreToAddLogging(store)
patchStoreToAddCrashReporting(store)

那么有没有一种更好的代码组织方式呢?此前,我们使用dispatchAndLog替换了dispatch, 如果我们不这样做,而是在函数中返回新的dispatch呢?

function logger(store) {
  let next = store.dispatch
  // 我们之前的做法:
  // store.dispatch = function dispatchAndLog(action) {
  return function dispatchAndLog(action) {
    console.log('dispatching', action)
    let result = next(action)
    console.log('next state', store.getState())
    return result
  }
}

然后我们在外部提供方法将它替换到store.dispatch中。

function applyMiddlewareByMonkeypatching(store, middlewares) {
  middlewares = middlewares.slice()
  middlewares.reverse()
  // 在每一个 middleware 中变换 dispatch 方法。
  middlewares.forEach(middleware =>
    store.dispatch = middleware(store)
  )
}

其实到了这里我们的中间件功能已经大体实现,如果想后续继续深入请参考redux官方文档

redux-saga

接下来我们来看管理应用程序副作用的中间件reudx-saga。他在redux中有很多使用场景,但是我们使用最多的还是用它来进行网络请求。

redux-saga使用了ES6的Generator功能,让异步的流程更易于读取,写入和测试。因此我们首先了解一下generator函数是什么?

Generator函数

形式上,Generator函数是一个普通函数,但是有两个特征。

一是,function关键字与函数名之间有一个星号;

二是,函数体内部使用yield表达式,定义不同的内部状态.

function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}
const hw = helloWorldGenerator();

Generator 函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号。不同的是,调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象。

下一步,必须调用遍历器对象的next方法,使得指针移向下一个状态。也就是说,每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。

换言之,Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行

hw.next()
// { value: 'hello', done: false }
hw.next()
// { value: 'world', done: false }
hw.next()
// { value: 'ending', done: true }
hw.next()
// { value: undefined, done: true }

实际使用场景

现在来看一个项目中的实际使用

//查询搜索列表
const requestLists = function*({ page, keyword, callback }) {
  try {
    appLoading(); // 展现加载框
    const body = {
      keyword: keyword,
      page: page,
      size: size,
    };
    const result = yield handleData.get(DataUrls.searchLists, body) // 发送网络请求
    if (result && result.success) {
      yield put(Action.fetchSearchListDone(lists)); // 请求成功保存数据
      callback && callback();
    } else {
      showModal((result && result.message) || '系统繁忙,请稍后');
      yield put(Action.fetchSearchListFailure(result)); //请求失败错误处理
    }
  } catch (err) {
    yield put(Action.fetchSearchListFailure(err)); //错误处理
    showModal('系统繁忙,请稍后');
  } finally {
    appFinish(); //关闭加载框
  }
};

上面是一个比较完整的请求处理过程,从发送请求到成功或失败处理都有包含到。

对上面的代码做一个解释,在这个函数中我们首先使用yield发起一个异步请求,这时middleware 会暂停 Saga,直到请求完成。 一旦完成后,不管是成功或者失败,middleware 会恢复 Saga 接着执行,直到遇到下一个 yield。当 try 报错时, 会执行到catch去捕获异常, 在这里遇到下一个yield,调用请求失败的Action,传入失败原因。请求成功时遇到下一个 yield 对象,调用请求成功的Action,传入结果。

put 就是我们称作副作用的一个例子。副作用是一些简单 Javascript 对象,包含了要被 middleware 执行的指令。 当 middleware 拿到一个被 Saga yield 的副作用,它会暂停 Saga,直到副作用执行完成,然后 Saga 会再次被恢复。

接下来我们需要去启动这个saga,为了做到这一点,我们将添加一个 listSaga,负责启动其他的 Sagas。在同一个文件中:

const listSagas = function* listSagas() {
  yield all([
    takeEvery('LIST_REQUESTLIST', requestLists),
  ]);
};
export default listSagas;

其中的辅助函数takeEvery用于监听所有的LIST_REQUESTLISTaction,在action执行的时候去启动相应的requestLists任务。 定义一个listSagas的原因就是我们这个文件中可能远不至这一个副作用函数,当定义了多个的时候,我们可以在all中添加一个takeEvery, 这样就会有两个Generators同时启动。在实际项目中因为项目所分的模块可能会有很多,因此对每个模块都定义一个sagas是很有必要的, 最终在sagas的最外层定义一个index.js文件用来将我们的所有模块整合在一起定义一个root,然后我们只有在 main.js 的 root Saga 中调用sagaMiddleware.run。就可以启动所有的sagas。

对于其他更加详细的redux-saga学习可以参考文档

以上就是redux功能强大的Middleware中间件使用学习的详细内容,更多关于redux中间件Middleware的资料请关注我们其它相关文章!

(0)

相关推荐

  • redux中间件之redux-thunk的具体使用

    redux的核心概念其实很简单:将需要修改的state都存入到store里,发起一个action用来描述发生了什么,用reducers描述action如何改变state tree .创建store的时候需要传入reducer,真正能改变store中数据的是store.dispatch API. 1.概念 dispatch一个action之后,到达reducer之前,进行一些额外的操作,就需要用到middleware.你可以利用 Redux middleware 来进行日志记录.创建崩溃报告.调用

  • 浅谈redux, koa, express 中间件实现对比解析

    如果你有 express ,koa, redux 的使用经验,就会发现他们都有 中间件(middlewares)的概念,中间件 是一种拦截器的思想,用于在某个特定的输入输出之间添加一些额外处理,同时不影响原有操作. 最开始接触 中间件是在服务端使用 express 和 koa 的时候,后来从服务端延伸到前端,看到其在redux的设计中也得到的极大的发挥.中间件的设计思想也为许多框架带来了灵活而强大的扩展性. 本文主要对比redux, koa, express 的中间件实现,为了更直观,我会抽取出

  • 浅谈Redux中间件的实践

    最近项目前端开发框架采用React+Redux进行实现,但是,如何异步访问服务器端,以及想要在开发过程中进行状态树日志的输出,所以怎么才能解决这两个问题? 采用Redux中间件 为什么要使用中间件 在利用Redux进行状态管理时,用户在UI层面触发行为,一个action对象通过store.dispatch派发到Reducer进行触发,接下来Reducer会根据type来更新对应的Store上的状态树,更改后的state会触发对应组件的重新渲染.如下图所示.在这个流程中,action对象是一个同步

  • 简单介绍react redux的中间件的使用

    用过react的同学都知道在redux的存在,redux就是一种前端用来存储数据的仓库,并对改仓库进行增删改查操作的一种框架,它不仅仅适用于react,也使用于其他前端框架.研究过redux源码的人都觉得该源码很精妙,而本博文就针对redux中对中间件的处理进行介绍. 在讲redux中间件之前,先用两张图来大致介绍一下redux的基本原理: 图中就是redux的基本流程,这里就不细说. 一般在react中不仅仅利用redux,还利用到react-redux: react-redux这里也不细说.

  • redux功能强大的Middleware中间件使用学习

    目录 引言 redux中的Middleware 记录日志 手动记录 redux-saga Generator函数 实际使用场景 引言 上一节我们学习了redux在实际项目的应用细节,这一节我们来学习redux中一个很重要的概念:中间件.我们会简单实现一个记录的中间件, 然后学习redux-saga这个异步请求中间件. redux中的Middleware redux中的中间件提供的是位于 action 被发起之后,到达 reducer 之前的扩展点. 你可以利用 Redux middleware

  • 功能强大的PHP POST提交数据类

    本文实例为大家分享了PHP功能强大的 POST提交数据类,供大家参考,具体内容如下 <?php class Request{ public static function post($url, $post_data = '', $timeout = 5){//curl $ch = curl_init(); curl_setopt ($ch, CURLOPT_URL, $url); curl_setopt ($ch, CURLOPT_POST, 1); if($post_data != ''){

  • Android组合控件实现功能强大的自定义控件

    通常情况下,Android实现自定义控件无非三种方式. Ⅰ.继承现有控件,对其控件的功能进行拓展. Ⅱ.将现有控件进行组合,实现功能更加强大控件. Ⅲ.重写View实现全新的控件 上文说过了如何继承现有控件来自定义控件:<Android继承现有控件拓展实现自定义控件textView>,这节我们来讨论第二个议题.怎么将控件组合来实现一个功能强大的自定义控件. 先看看创建组合控件的好处吧,创建组合控件能够很好的创建具有组合功能的控件集合.那我们一般又是怎么做的了,一般我们来继承一个合适的ViewG

  • 上手简单,功能强大的Python爬虫框架——feapder

    简介 feapder 是一款上手简单,功能强大的Python爬虫框架,使用方式类似scrapy,方便由scrapy框架切换过来,框架内置3种爬虫: AirSpider爬虫比较轻量,学习成本低.面对一些数据量较少,无需断点续爬,无需分布式采集的需求,可采用此爬虫. Spider是一款基于redis的分布式爬虫,适用于海量数据采集,支持断点续爬.爬虫报警.数据自动入库等功能 BatchSpider是一款分布式批次爬虫,对于需要周期性采集的数据,优先考虑使用本爬虫. feapder除了支持断点续爬.数

  • asp.net(C#)实现功能强大的时间日期处理类完整实例

    本文实例讲述了asp.net(C#)实现功能强大的时间日期处理类.分享给大家供大家参考,具体如下: using System; using System.Data; using System.Configuration; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts

  • php基于PDO实现功能强大的MYSQL封装类实例

    本文实例讲述了php基于PDO实现功能强大的MYSQL封装类.分享给大家供大家参考,具体如下: class CPdo{ protected $_dsn = "mysql:host=localhost;dbname=test"; protected $_name = "root"; protected $_pass = ""; protected $_condition = array(); protected $pdo; protected $f

  • C#实现功能强大的中国农历日历操作类

    本文实例讲述了C#实现功能强大的中国农历日历操作类.分享给大家供大家参考.具体如下: 这个C#类定义了中国农历日历,除了可以输入正常的日历外还可以获得指定年份的生肖.返回年份的干支以及星座.二十四节气.二十八星宿.常用节日等. 部分代码片段如下 /// <summary> /// 传回农历y年闰月的天数 /// </summary> private static int leapDays(int y) { if (leapMonth(y) != 0) { if ((lunarInf

  • 打造个性化的功能强大的Jquery虚拟键盘(VirtualKeyboard)

    最近做项目,我负责做网页前端,客户需要利用触摸屏进行操作,不外接鼠标键盘,但要求能录入文字,包括数字,英文,中文.思考了一下,决定用JS实现虚拟键盘. 首先上网搜索了一下JS虚拟键盘,在经过仔细筛选后,相中了VirtualKeyboard,一款功能强大的JS虚拟键盘插件. 先简单介绍一下VirtualKeyboard,它内置了100多种键盘布局和200多种输入法,9套可选皮肤方案,而且支持自建输入法,功能相当强大. 先附上下载地址,目前的最新版本3.94:http://www.coralloso

  • PHP中功能强大却很少使用的函数实例小结

    本文实例讲述了PHP中功能强大却很少使用的函数.分享给大家供大家参考,具体如下: call_user_func_array - 让参数以数组的形式调用一个函数 call_user_func - 调用一个存在的函数 create_function - 建立一个函数 func_get_arg - 获取函数中某个参数的值 func_get_args - 获取函数的所有参数并组成数组 func_num_args - 获取一个函数的参数个数 function_exists - 判定一个函数是否存在 get

  • Laravel框架控制器的middleware中间件用法分析

    本文实例讲述了Laravel框架控制器的middleware中间件用法.分享给大家供大家参考,具体如下: 场景:活动开始前只能访问宣传页面,开始后才可以访问其他页面: 步骤: 新建中间件, 注册中间件, 使用中间件, 中间件的前置和后置操作. 控制器: public function activity0(){ return '活动快要开始啦,敬请期待'; } public function activity1(){ return '活动页面1'; } public function activi

随机推荐