Redux:从action到saga
前端应用消失的部分一个现代的、使用了redux的前端应用架构可以这样描述:
const render = (state) => components
const reducer = (oldState,action) => newState
这看起来容易理解。但是当需要处理异步的action(在函数式编程里称为副作用)的时候事情就没有这么简单了。 为了解决这个问题,redux建议使用中间件(尤其是thunk)。基本上,如果你需要出发副作用(side effects),使用一种特定的action生成方法:一种返回一个方法的方法,可以实现任意的异步访问并分发任意你想要的action。 使用这个方式会很快导致action生成方法变得复杂并难以测试。这个时候就需要redux-saga了。在redux-saga里saga就是一个可声明的组织良好的副作用实现方式(超时,API调用等等。。)所以不用再用redux-thunk中间件来写,我们用saga来发出action并yield副作用。 这样不复杂?action creator这样的写法不是更简单?虽然看起来是这样的,但是NO! 我们来看看如何写一个action creator来获取后端数据并分发到redux store。 function loadTodos() { return dispatch => { dispatch({ type: 'FETCH_TOTOS' }); fetch('/todos').then(todos => { dispatch({ type: 'FETCH_TODOS',payload: todos }); }); } } 这是最简单的thunk action creator了,并且如你所见,唯一测试这个代码的方法是模拟获取数据的方法。 我们来看看用saga代替action creator获取todo数据的方法: import { call,put } from 'redux-saga'; function* loadTodos() { yield put({ type: 'FETCH_TODOS' }); const todos = yield call(fetch,'/todos'); yield put({ type: 'FETCH_TODOS',payload: todos }); } 正如你所见一个saga就是一个生成副作用(side effects)的generator。我(作者)更加倾向于把整个generator叫做纯generator,因为它不会实际执行副作用,只会生成一个要执行的副作用的描述。在上面的例子中我用了两种副作用:
现在,测试这个saga就非常的容易了: import { call,put } from 'redux-saga'; const mySaga = loadTodos(); const myTodos = [{ message: 'text',done: false }]; mySaga.next(); expect(mySaga.next().value).toEqual(put({ type: 'FETCH_TOTOS' })); expect(mySaga.next().value).toEqual(call(fetch,'/todos')); expect(mySaga.next().value).toEqual(put({ type: 'FETCH_TODOS',payload: myTodos })); 触发一个sagathunk的action creator在分发它返回的方法的时候就会触发。saga不同,它们就像是运行在后台的守护任务(daemon task)一样有自己的运行逻辑(by Yasine Elouafi redux-saga的作者)。 所以,我们来看看如何在redux应用里添加saga。 import { createStore,applyMiddleware } from 'redux'; import sagaMiddleware from 'redux-saga'; const createStoreWithSaga = applyMiddleware( sagaMiddleware([loadTodos]) )(createStore); let store = createStoreWithSaga(reducer,initialState); 绑定saga一个saga本身就是一个副作用,就如同redux的reducer一样,绑定saga非常简单(但是很好的理解ES6的generator是非常有必要的)。 在之前的例子里, import { fork,take } from 'redux-saga'; function* loadTodos() { yield put({ type: 'FETCHING_TODOS' }); const todos = yield call(fetch,'/todos'); yield put({ type: 'FETCHED_TODOS',payload: todos }); } function* watchTodos() { while(yield take('FETCH_TODOS')) { yield fork(loadTodos); } } // 我们需要更新saga常量 createStoreWithSaga = applyMiddleware( sagaMiddleware([watchTodos]) )(createStore); 上例用到了两个特殊的effect:
结语给前端应用添加redux和redux-saga的流程是这样的:
const render = (state) => components
const reducer = (oldState,action) => newState
function* saga() { yield effect; }
原文链接:https://riad.blog/2015/12/28/... (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |