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(...) } };
更多web前端开发知识,请查阅 HTML中文网 !!
以上就是react中间件thunk和saga的区别是什么?的详细内容,更多请关注易知道|edz.cc其它相关文章!