基于React与Redux Saga的权限验证应用(二)

作者 Perkin 日期 2017-09-12
基于React与Redux Saga的权限验证应用(二)

本文是基于React and Redux Sagas Authentication App Tutorial翻译整理,并总结而来,目的是学习redux-saga以及redux-form以及JWT形式的验证方式。

基于React与Redux Saga的权限验证应用(一)主要介绍了项目的搭建,redux的基础知识,以及redux-form,本文开始介绍redux-saga,在项目中的运用。

Redux-Saga

在项目中运用redux-saga

redux-saga是一个redux中间件,主要有下面的特点:

  • 集中解决redux副作用的问题
  • 依靠generator/yield来实现异步
  • 类redux-thunk中间件
  • watch/worker(监听->执行)的工作形式

下面我们来看看项目中是如何实现的

import { takeLatest } from 'redux-saga/effects'
import { SIGNUP_REQUESTING } from './constants'
// watcher会监听运行的SIGNUP_REQUESTING action
function* signupFlow (action) {}
// 监听SIGNUP_REQUESTING,当我们dispatch时,调用signupFlow()
function* signupWatcher () {
// takelatest会发起最新的一次调用
yield takeLatest(SIGNUP_REQUESTING, signupFlow)
}
export default signupWatcher

以上过程可以简化为:

1)等待直到SIGNUP_REQUESTING这个动作被分发

2)当动作被接收后,调用signupFlow()

3)signupFlow()会在每一异步过程以generator单步执行,方便地处理复杂异步问题。

//src/signup/sagas.js
import { call, put, takeLatest } from 'redux-saga/effects'
import { handleApiErrors } from '../lib/api-errors'
import {
SIGNUP_REQUESTING,
SIGNUP_SUCCESS,
SIGNUP_ERROR,
} from './constants'
const signupUrl = `${process.env.REACT_APP_API_URL}/api/Clients`
function signupApi (email, password) {
return fetch(signupUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ email, password }),
})
.then(handleApiErrors)
.then(response => response.json())
.then(json => json)
.catch((error) => { throw error })
}
// ..
//src/lib/api-errors.js
// 手动处理error
export function handleApiErrors (response) {
if (!response.ok) throw Error(response.statusText)
return response
}

redux-saga的常用api

  • Saga辅助函数
    • takeEvery:允许多个请求同时启动。在某个特定时刻,我们可以启动一个新的请求任务, 尽管之前还有一个或多个 请求尚未结束。
    • takeLastest:想得到最新那个请求的响应
  • 声明式Effects
    我们从generator里yield纯Javascript对象以表达Saga逻辑,称这些对象为Effects。可以当作是发送给middleware的指令以执行某些操作,即调用某些异步函数,发起一个action到store。

    • call:通过call,我们不立即执行异步调用,而是创建一个描述结果的信息
      这与redux中的action creator一样,创建一个将被Store执行的、描述的action的纯文本信息

      当然call同样支持调用对象方法,比如

      yield call([obj, obj.method], arg1, arg2, ...) // 如同 obj.method(arg1, arg2 ...)
    • apply
      与call类似,提供了另外一种调用

      yield apply(obj, obj.method, [arg1, arg2, ...])
    • cps:以上call跟apply适合返回promise结果的函数,而cps可以用来处理Node风格的函数,cps 表示的是延续传递风格(Continuation Passing Style))。

      import { cps } from 'redux-saga'
      const content = yield cps(readFile, '/path/to/file')
    • take:与call跟put类似,它创建另一个命令对象,告诉middleware等待一个特定的action。与action被推向(pushed) 任务处理函数不同,Saga 是自己主动拉取action。好处:1. 能够实现一些使用传统的push方法做非常规事情的控制流2. 可以使用熟悉的同步风格来描述我们的控制流,比如:

      //使用拉取(pull)模式,我们可以在同一个地方写控制流。
      function* loginFlow() {
      while(true) {
      yield take('LOGIN')
      // ... perform the login logic
      yield take('LOGOUT')
      // ... perform the logout logic
      }
      }
    • fork:表示无阻塞调用,当我们fork一个任务,任务会在后台启动,调用者也可以继续它自己的流程,而不用等待被 fork 的任务结束。

    • cancel:取消fork任务
    • race:在多个 Effects 之间触发一个竞赛,另一个有用的功能是,它会自动取消那些失败的 Effects
  • 发起action

    • put:用于创建 dispatch Effect

      这样通过以上声明式的解决方案,我们可以很轻易的测试generator。这次就不深入讨论。

redux-saga跟redux-thunk的区别

redux-thunk

Redux本身只能处理同步的Action,但可以通过中间件来拦截处理其它类型的action,比如函数(Thunk),再用回调触发普通Action,从而实现异步处理,在这点上所有Redux的异步方案都是类似的。

redux-thunk最大的特点是:redux-thunk 以延迟执行的方式把副作用的责任推卸到用户身上。

  • Sagas 是通过 Generator 函数来创建的,意味着可以用同步的方式写异步的代码;
  • Thunks 是在 action 被创建时才调用,Sagas 在应用启动时就开始调用,监听action 并做相应处理; (通过创建 Sagas 将所有的异步操作逻辑收集在一个地方集中处理)
  • 启动的任务可以在任何时候通过手动取消,也可以把任务和其他的 Effects 放到 race 方法里可以自动取消

redux-sage相比于redux-thunk的好处:

  1. 易于测试
  2. 更复杂的控制流

redux-saga的优缺点

缺点:

  • 由于saga采用generator配合fetch来完成异步,而fetch不强迫我们捕获异常,这往往会造成异常发生时难以发现原因。
  • generator 的调试环境比较糟糕,babel 的 source-map 经常错位,经常要手动加 debugger 来调试。
  • 使用的其它异步中间件,或许难以和 redux-saga 搭配良好

优点:

  • 保持 action 的简单纯粹,流程拆分更细,所有异步的action 或者特殊要求的action都在sagas中做统一处理,流程逻辑更清晰,模块更干净;
  • 声明式的 Effects,能容易地测试 Generator 里所有的业务逻辑
  • 以用同步的方式写异步代码,可以做一些async 函数做不到的事情 (无阻塞并发、取消请求)
  • 可以通过监听Action 来进行前端的打点日志记录,减少侵入式打点对代码的侵入程度

    参考链接


redux-saga 实践总结