redux saga 的基本使用和单元测试

4 篇文章 0 订阅
1 篇文章 0 订阅

1、redux-saga
是一个用于管理应用程序 Side Effect(副作用,例如异步获取数据,访问浏览器缓存等)的 library,它的目标是让副作用管理更容易,执行更高效,测试更简单,在处理故障时更容易。
换句话说:

  • redux-saga是一个redux的中间件,而中间件的作用就是为redux提供额外的作用。
  • 在reducers中所有的操作都是同步并且是纯粹的,即reducer都是纯函数,纯函数就是指一个函数的返回结果只依赖它的参数,并且在执行过程中不会对外部产生副作用,即给他传什么,就吐出什么
  • 但是在实际的应用开发中,我们希望做一些异步的(如ajax请求)且不纯粹的操作(如改变外部的状态),这些在函数式编程中被称为“副作用”。
  • redux-saga就是用来处理上述副作用(异步任务)的一个中间件,他是一个接收事件,并可能触发新事件的过程管理者,为你的应用管理复杂的流程

2、redux-saga工作原理

  • saga采用generator函数来yield Effects(包含指令的文本对象)
  • Generator函数的作用是可以暂停执行,再次执行的时候可以从上次暂停的地方继续执行
  • Effect是一个简单的对象,该对象包含了一些给middleware解释执行的信息
  • 你可以通过使用effects Api 如 fork,call,take,put,cancel等来创建Effect

3、redux-saga的分类

  • worker saga做实际的工作,如调用API,进行异步请求,获取异步封装的结果
  • watcher saga监听被dispatch的actions,当接收到action或者知道其被触发时,调用worker执行任务
  • root saga 是启动saga的唯一入口

为了能跑起 Saga,我们需要使用 redux-saga 中间件将 Saga 与 Redux Store 建立连接,下面,我们来用代码来做相应的示例:

首先安装saga

 npm install --save redux-saga

然后在redux的index文件中引入:

import { createStore, applyMiddleware } from 'redux';
//引入redux-saga的中间件
import createSagaMiddleWare from 'redux-saga';
import reducers from './reducers';
import rootSaga from './saga/index';
// 执行中间件函数
let sagaMiddleWare = createSagaMiddleWare();
//通过应用中间件的方式进行引用saga
const store = applyMiddleware(sagaMiddleWare)(createStore)(reducers);
//执行根saga
sagaMiddleWare.run(rootSaga);

export default store;

然后在redux目录中建立一个saga目录:
在这里插入图片描述
在这个文件中,我们就可以去写saga的相关代码了,我们先写一个基本的saga案例:

因为需要在创建厂库的时候我们需要使用run方法去执行一个根saga,所以我们需要导出一个saga方法

// => 会立即打印出 hello saga
export default function* (){
    console.log('hello saga');
}

这样子,一个基本的saga就完成了,但是只是这样子肯定还是远远不够的,下面,我们就先来说说redux-saga常用的几个方法吧:

import {take,call,put,select,fork,takeEvery,takeLatest,cps} from 'redux-saga/effects'

take方法
take这个方法,是用来监听action,返回的是监听到的action对象,take方法类似于一次性使用。
例如:

const watch2 = function* (){
    let actions = yield take(types.ADD);
    console.log('动作:',actions)
}

//监听saga
export default function* rootSaga() {
    yield all([
        watch2(),
        helloSaga(),
        watchIncrementAsync()
    ])
}

每次我派发了Add这个动作之后,都会执行watch2这个saga,并且打印出
// => 动作: {type: “ADD”, payload: 20}

call方法
call用来调用异步函数,将异步函数和函数参数作为call函数的参数传入,返回一个js对象。saga引入他的主要作用是方便测试,同时也能让我们的代码更加规范化。

import { call } from 'redux-saga/effects'
function* search() {
  const result = yield call(Api.fetch, '/result') // 不传this
  //const result = yield call([this, 参数列表...]) // 传this
  // ...
}

put方法
put方法的作用主要是用来派发一个action,类似于dispatch方法的作用:

const workSaga = function* (){
    let msg = yield delay(3000);
    // 派发一个动作
    yield put({type: types.DECREMENT,payload:100})
}

select方法
put方法与redux中的dispatch相对应,同样的如果我们想在中间件中获取state,那么需要使用select。select方法对应的是redux中的getState,用户获取store中的state,使用方法:

const workSaga = function* (){
    //可以获取到store中的状态
    let state = yield select();
    console.log('saga state:',state)

    let msg = yield delay(3000);
    // 派发一个动作
    yield put({type: types.DECREMENT,payload:100})
}

fork方法
非阻塞任务调用机制:上面我们介绍过call可以用来发起异步操作,但是相对于generator函数来说,call操作是阻塞的,只有等promise回来后才能继续执行,而fork是非阻塞的 ,当调用fork启动一个任务时,该任务在后台继续执行,从而使得我们的执行流能继续往下执行而不必一定要等待返回。

const delay2 = function(ms){
    return new Promise((resolve,reject) => {
        setTimeout(()=>{
            //后打印
            console.log('xxxx')
        },ms)
    })
}
const testWork = function* (){
     yield fork(delay2,2000);
    console.log('last') // => 会先打印,fork不会阻塞流程,call会
}

takeEvery方法
takeEvery用于监听相应的动作并执行相应的方法,只要监听到对应动作,那么他就会一直执行对应的监听函数,不像take方法只会执行一次,并且,它还可以同时监听到多个相同的action。

const watch2 = function* (){
    // 可以监听多个动作,只要监听到ADD方法,那么对应的后面的函数就会去执行
    yield takeEvery(types.ADD,testWork)
    yield takeEvery(types.ADD,testWork2)
}

takeLatest方法
takeLatest方法跟takeEvery是相同方式调用,与takeEvery不同的是,takeLatest是会监听执行最近的那个被触发的action。

在实际的项目开发中来我们只需要按照如下的方式来使用redux-saga:

import { all, put, takeEvery } from 'redux-saga/effects';
import * as types from './../action-types';

//工作saga
const workSaga = function* (){
    // 派发一个动作
    yield put({type: types.DECREMENT,payload:100})
}

//监听saga
const watchIncrementAsync = function* (){
    // 当监听到ADD这个动作发生的时候,会派发一个新的动作
    yield takeEvery(types.ADD,workSaga)
}

//导出一个根saga
export default function* rootSaga() {
    yield all([
        watchIncrementAsync()
    ])
}

cps方法
同call方法基本一样,但是用处不太一样,call一般用来完成异步操作,cps可以用来完成耗时比较长的io操作等。

单元测试
如果想测试一下我们写的saga的功能,也可以写一些单元测试去测试,如下:
首先,安装相关包

npm i @babel/core @babel/node @babel/plugin-transform-modu
les-commonjs --save-dev

 * @babel/core 转换es6的语法
 * @babel/node 转换node环境下的es6语法
 * @babel/plugin-transform-modules-commonjs   => babel插件,让node来支持es6的写法

然后在package.json文件中配置一个测试命令:
在这里插入图片描述
然后我们开始写我们的单元测试
1、比如我要测试我的saga输出结果是不是会和单元测试中预想的结果一样,如下:
测试代码:

test('incrementAsync saga test',function(assert){
    let gen = incrementAsync();
    // 第一次不会相等
    //测试是否相等   前两个参数是相比较的两个值,第三个参数是测试错误的提示 
    assert.deepEqual(gen.next().value,call(delay , 3000),'the result will be true after two seconds '); // fail

    // 第二次应该相等
    assert.deepEqual(gen.next().value,put({type: types.ADD,payload: 1000}),'the second result will be equal '); // pass

    //结束
    assert.end()
})

saga:

import { put } from '@redux-saga/core/effects';
import * as types from './../action-types';
import { delay } from './../../utils/index';


export function* incrementAsync(){
    let msg = yield delay(2000);
    console.log('msg:',msg);
    yield put({type: types.ADD,payload: 1000})
}

我们在终端中运行

npm run test

最后得到的结果是:
在这里插入图片描述
表示的当前的测试失败了,说明我们写的测试函数或者代码有问题。

2、我们也可以写一个方法去模拟node中的readFile方法

// 模拟node中的方法,读取文件
export const readFile = function(fileName,callback){
    setTimeout(()=>{
        callback(null,fileName + ':content');
    },1000)
}

然后写一个saga来使用这个函数:
使用cps来获取回调中的值

export function* readAsync(){
    let content = yield cps(readFile,'README.md');
    // cps 会获取到执行方法回调函数中的值
    console.log(content) // =>打印出 README.md: content

}

然后写一个单元测试来测测这个功能:

test('cps execute readFile',function(assert){
    let gen = readAsync();
    assert.deepEqual(gen.next().value,cps(readFile,'README.md'),'the readFile result will be equal '); // pass
    assert.deepEqual(gen.next(),{value: undefined,done:true},'should done '); // pass
    //结束
    assert.end()
})

最后全部单元测试的运行结果为:
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值