redux中间件saga和thunk的区别

redux中的action仅支持原始对象(plain object),处理有副作用的action,需要使用中间件。中间件可以在发出action,到reducer函数接受action之间,执行具有副作用的操作。

之前一直使用redux-thunk处理异步等副作用操作,在action中处理异步等副作用操作,此时的action是一个函数,以dispatch,getState作为形参,函数体内的部分可以执行异步。通过redux-thunk来处理异步,action可谓是多种多样,不利于维护。

本文主要介绍一下redux-saga ,并在此基础上比较redux-thunk(async/await)

1 . redux-thunk 的使用与缺点
(1)redux-thunk的使用
thunk是redux作者给出的中间件,实现极为简单,10多行代码:

function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => next => action => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }
    return next(action);
  };
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;

这几行代码做的事情也很简单,判别action的类型,如果action是函数,就调用这个函数,调用的步骤为:

action(dispatch, getState, extraArgument);

发现实参为dispatch和getState,因此我们在定义action为thunk函数是,一般形参为dispatch和getState。

(2)redux-thunk的缺点
thunk的缺点也是很明显的,thunk仅仅做了执行这个函数,并不在乎函数主体内是什么,也就是说thunk使得redux可以接受函数作为action,但是函数的内部可以多种多样。比如下面是一个获取商品列表的异步操作所对应的action

store里面先引入中间件

import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';

const initialState = {};
const middleware = [thunk];

export const store = createStore(
	rootReducer, 
	initialState,
	compose(
		applyMiddleware(...middleware),
		window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
	)
);

action文件里

import { FETCH_POSTS, NEW_POST } from './type'
export const fetchPosts = () => dispatch => {
		fetch("https://jsonplaceholder.typicode.com/posts")
        .then(res => res.json())
        .then(posts =>
        	dispatch({
        		type: FETCH_POSTS,
        		payload: posts
        	})
        )
}
export const createPost = postData => dispatch => {
	fetch("https://jsonplaceholder.typicode.com/posts",{
        method: "POST",
        headers:{
            "content-type":"application/json"
        },
        body:JSON.stringify(postData)
    })
    .then(res => res.json())
    .then(post => 
    	dispatch({
    		type: NEW_POST,
    		payload: post
    	})
    )
}

从这个具有副作用的action中,我们可以看出,函数内部极为复杂。如果需要为每一个异步操作都如此定义一个action,显然action不易维护。
action不易维护的原因:
I)action的形式不统一
II)就是异步操作太为分散,分散在了各个action中

2 .redux-saga的使用
在redux-saga中,action是plain object(原始对象),并且集中处理了所有的异步操作,下面我们以redux-saga的官方例子shopping-cart为例,来说说redux-saga的使用。
shopping-cart例子很简单,展示的是如下过程:
商品列表——>添加商品——>购物车——>付款
具体的页面,如下:

显然,这里有两个明显的异步操作需要执行:
获取商品列表和付款
用getAllProducts()和checkout()来表示,如果用thunk,那么这两个异步的操作分属于两个不同的action中,但是在saga中,它们是集中处理的。
使用saga,我们先生成一个集中处理异步的saga.js文件:

import { put, takeEvery, call } from 'redux-saga/effects'
import { CHANGE_HITOKOTO_RESP, CHANGE_HITOKOTO } from '../actions/Hitokoto'
import hitokotoApi from '../services/hitokoto'
function gethitokoto() {
    return hitokotoApi.get().then(resp => resp)
}
export function* changeHitokoto() {
    const defaultHitokoto = {
        'id': 234,
        'hitokoto': '没有谁能够永远坚强下去的,每个人都会有疲累的无法站起的时候。世间的故事,就是为了这一刻而存在的哦。',
        'type': 'a',
        'from': '文学少女',
        'creator': '酱七',
        'created_at': '1468605914'
    };
    try {
        const data = yield call(gethitokoto);
        const hitokotoData = JSON.parse(data);
        yield put({ type: CHANGE_HITOKOTO_RESP, hitokotoData });
    } catch (error) {
        yield put({ type: CHANGE_HITOKOTO_RESP, hitokotoData: defaultHitokoto });
    }
}
export default function* shici() {
    yield takeEvery(CHANGE_HITOKOTO, changeHitokoto)
}

抛去其他部分(具体用法我们待会解释),我们看到在saga.js中集中了这两个异步操作getAllProducts()和checkout()
此外,在saga中的action跟原始对象是完全相同的,我们来看saga中的action creator :

export const GET_ALL_PRODUCTS = 'GET_ALL_PRODUCTS'
export function getAllProducts() {
  return {
    type: GET_ALL_PRODUCTS,
  }
}

上述的action creator中,创建的action是一个plain object,跟我们在redux中同步action的样式是统一的。

3 . redux-saga的API

redux-saga是通过ES6中的generator实现的(babel的基础版本不包含generator语法,因此需要在使用saga的地方import ‘babel-polyfill’)。redux-saga本质是一个可以自执行的generator。

(1)redux-saga中的Effect
redux-saga中定义了Effect,Effect是什么呢,本质就是一个特定的函数,返回的是纯文本对象。简单理解,通过Effect函数,会返回一个字符串,saga-middleware根据这个字符串来执行真正的异步操作,可以具体表现成如下形式:
异步操作——>Effect函数——>纯文本对象——>saga-middleware——>执行异步操作
因为Effect的存在,方便saga测试异步操作。

(2)Effect具体函数
Effect函数有很多个,在redux-saga/effects提供,主要包括call,fork,put,take,takeEvery、takeLatest,select等,它们都与middleware中的操作一一对应。
I)call 和 fork
call和fork表示异步调用,其中call表示的是阻塞调用,fork表示的是非阻塞调用。
II)put和select
put对应的是middleware中的dispatch方法,参数是一个plain object,一般在异步调用返回结果后,接着执行put。select相当于getState,用于获取store中的相应部分的state。
III)take、takeEvery、takeLatest
redux-saga中如果在非阻塞调用下(fork),那么遵循的是worker/watcher模式,通过take可以监听某个action是否被发起,此外通过take结合fork,可以实现takeEvery和takeLatest的效果。
如果一个异步操作的action被发起多次,takeEvery会执行多次action,而takeLatest只会执行最近的一次。

4 . redux-saga的优缺点
优点:
(1)集中处理了所有的异步操作,异步接口部分一目了然
(2)action是普通对象,这跟redux同步的action一模一样
(3)通过Effect,方便异步接口的测试
(4)通过worker 和watcher可以实现非阻塞异步调用,并且同时可以实现非阻塞调用下的事件监听
(5) 异步操作的流程是可以控制的,可以随时取消相应的异步操作。
缺点:太复杂,学习成本较高

5 .redux-thunk(async/await)
如果在redux-thunk中,action是一个async函数,通过约定一些异步操作时所遵循的规则,即使不能集中展示所有的异步操作,但是通过约定我们可以减少函数action的复杂度,我们最初获取商品列表的那个action,可以改写成:

export default function(){
  return  function(dispatch){
    await result=fetch(...)
    result.then(...)
  }
};
发布了90 篇原创文章 · 获赞 5 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/me_never/article/details/102588042