摘要:特點集中處理副作用問題異步實現(xiàn)為監(jiān)聽執(zhí)行的工作形式主要是借鑒模式和使用進行實現(xiàn)的。返回的遍歷器對象,可以依次遍歷函數(shù)內(nèi)部的每一個狀態(tài)。為了方便,下文中是的簡稱。若任務仍在運行中則為任務拋出的錯誤。由于循環(huán),再次執(zhí)行。
介紹redux-saga使用和常用api介紹的文章很多,但是真正介紹原理的卻很少,下面我用自己的思路講一下redux-saga的執(zhí)行過程。源碼有很多刪減,感興趣的可自行查看。
1.react-saga是什么redux-saga是用于管理?Side Effects(異步操作/副作用)的redux中間件
2.什么是redux中間件redux中間件是通過改變store.dispatch使dispatch(action)時能處理副作用。在這里我們用于處理異步操作。
具體可參考http://www.ruanyifeng.com/blo...。如果對中間件不熟悉,請一定把這篇文章看完再往下進行。
集中處理redux副作用問題
異步實現(xiàn)為generator
watch/worker(監(jiān)聽->執(zhí)行)的工作形式
redux-saga主要是借鑒 sagas模式 和使用 generators 進行實現(xiàn)的。
首先,我們講講sagas模式:解決長時間運行的事務導致的系統(tǒng)運行效率以及并發(fā)能力的問題,將業(yè)務分為多個獨立的事務,每個業(yè)務都會確保擁有修正事務(回滾),如果業(yè)務過程遇到了錯誤的情況并且無法繼續(xù),它就可以執(zhí)行修正事務來修正已經(jīng)完成的步驟,這樣以保證最終的一致性。舉個栗子:A事務需要等待B事務完成之后才能執(zhí)行,如果B事務需要的時間很長,那么A事務就要等很久才能執(zhí)行,如果用sagas模式,可以把A事務和B事務分別執(zhí)行,如果B事務執(zhí)行失敗,則把A事務進行回滾。
sagas模式參考文檔:https://blog.csdn.net/ethanwh...
redux-saga中對sagas的借鑒:在redux-saga里,一個saga就是一個生成器函數(shù)(generator function),可以在系統(tǒng)內(nèi)無限期運行。當特定action被dispatch時,saga就可以被喚醒。saga也可以繼續(xù)dispatch額外的actions,也可以接入程序的單一狀態(tài)樹。也能從主應用程序啟動,暫停和取消。借鑒了sagas的滿足特殊條件的長事務,可回滾。
接下來講講generator
Generator 函數(shù)是 ES6 提供的一種異步編程解決方案。執(zhí)行 Generator 函數(shù)會返回一個遍歷器對象,也就是說,Generator 函數(shù)除了狀態(tài)機,還是一個遍歷器對象生成函數(shù)。返回的遍歷器對象,可以依次遍歷 Generator 函數(shù)內(nèi)部的每一個狀態(tài)。形式上,Generator 函數(shù)是一個普通函數(shù),但是有兩個特征。一是,function關(guān)鍵字與函數(shù)名之間有一個星號;二是,函數(shù)體內(nèi)部使用yield表達式,定義不同的內(nèi)部狀態(tài)(yield在英語里的意思就是“產(chǎn)出”)。為了方便,下文中g(shù)en是generator的簡稱。
舉個簡單的例子
function* helloWorldGenerator() { yield "hello"; yield "world"; return "ending"; } var hw = helloWorldGenerator();
執(zhí)行結(jié)果
hw.next() // { value: "hello", done: false } hw.next() // { value: "world", done: false } hw.next() // { value: "ending", done: true } hw.next() // { value: undefined, done: true }
再來一個復雜一點的傳值的例子
function* foo(x) { var y = 2 * (yield (x + 1)); var z = yield (y / 3); return (x + y + z); } var b = foo(5);
執(zhí)行結(jié)果
b.next() // { value:6, done:false } b.next(12) // { value:8, done:false } b.next(13) // { value:42, done:true }
具體可參考文檔http://es6.ruanyifeng.com/#do...,這里很重要,請了解一下。
了解完上面的基礎,下面我們開始講redux-saga的執(zhí)行過程。
我們以一個demo的形式進行分析。
需要解決幾個問題:
怎么執(zhí)行監(jiān)聽執(zhí)行?
如何循環(huán)把每一步執(zhí)行完?
怎么處理下一次任務?
當我們點擊按鈕時,會從后端請求接口,將返回的數(shù)據(jù)更新到頁面上
我們先自己實現(xiàn)一個中間件解決這個需求:
import axios from "axios"; const takers = []; // 存generator // 執(zhí)行g(shù)en function runGen(dispatch, gen) { // 防止無限循環(huán) if (!takers.length) { return; } // 遍歷執(zhí)行g(shù)enerator takers.forEach((taker, key) => { const g = taker(); // 刪除已執(zhí)行的taker takers.splice(key, 1); // 執(zhí)行yield axios.post并返回一個promise const userPromise = g.next().value; userPromise.then((user) => { // 把{dispatch, user}設置為上一步的返回值,并執(zhí)行yield dispatch g.next({ dispatch, user }); // 執(zhí)行yield takers g.next() }); }) } export default function fetchMiddleware(_ref2) { var getState = _ref2.getState, dispatch = _ref2.dispatch; // 初始化時注冊taker,把generator添加到takers,用于dispatch時執(zhí)行 fetchMiddleware.run = () => takers.push(gen) // 改變dispatch return (next) => { return (action) => { // dispatch時執(zhí)行這里 var result = next(action); runGen(dispatch, gen) return result; }; }; return fetchMiddleware; }
怎么用呢
import fetchMiddleware from "./fetchMiddleware"; // 初始化 fetchMiddleware.run()
// 需要執(zhí)行的gen function* gen(){ const { dispatch, user } = yield axios.post("http://rest.learncode.academy/api/wetern/users"); const { data } = user; yield dispatch({ type: "UPDATE_USER", payload: data }) yield takers.push(gen) }
現(xiàn)在我們來看看這個中間件實現(xiàn)步驟
點擊按鈕,執(zhí)行dispatch({ type: "FEATCH_USER })
對于上面那個例子,我們用gen.next()實現(xiàn)一步一步執(zhí)行。
初始化時把gen添加到takers中,這樣做的目的是點擊按鈕的時候可以執(zhí)行g(shù)enerator
點擊按鈕的時候,獲取generator,然后從takers中刪除,防止dispatch({ type: "UPDATE_USER", payload: data })也就是更新user的時候再次執(zhí)行g(shù)enerator,造成循環(huán)調(diào)用
gen.next()也就是執(zhí)行yield axios.post("xxx"),這里返回的是一個promise
在promise.then中調(diào)用gen.next({ dispatch, user }),實際上是調(diào)用yield dispatch({ type: "UPDATE_USER", payload: data })
最后調(diào)用gen.next,實際上調(diào)用yield takers.push(gen)。這里是為了把generator添加到takers中,用于下一次上面第二步的時候用。
這里我們簡單的實現(xiàn)了這個需求,redux-saga提供了更多更強大的api,下面我們看看redux-saga是怎么實現(xiàn)的吧。
先來看我們代碼中如何使用redux-saga吧
./index.js import { put, call, take } from "redux-saga/effects"; import { createStore, applyMiddleware } from "redux"; import createSagaMiddleware from "redux-saga"; // 創(chuàng)建action const action = type => store.dispatch({ type }) // 創(chuàng)建redux-saga中間件 const sagaMiddleware = createSagaMiddleware() // 生成store const store = createStore(rootReducer, applyMiddleware(sagaMiddleware)) // 執(zhí)行redux-saga中間件 sagaMiddleware.run(rootSaga) function render() { ReactDOM.render(action("FETCH_USER")} />, document.getElementById("root"), ) }
./saga.js // 創(chuàng)建saga export function* rootSaga() { while(true) { yield take("FETCH_USER"); const { data } = yield call(axios.post, "http://rest.learncode.academy/api/wetern/users"); yield put({ type: "UPDATE_USER", payload: data }) } }
首先引入redux-saga中間件,其實它就是redux的一個中間件,通過改變dispatch(action),使我們在發(fā)起action的時候處理異步操作。
function sagaMiddleware(_ref2) { var getState = _ref2.getState, dispatch = _ref2.dispatch; // next = store.dispatch 從redux中間件得出 return function (next) { return function (action) { // dispatch(action) dispatch的時候會到這一步 var result = next(action); // hit reducers // emitter 簡單的事件對象,subscribe訂閱/emit觸發(fā) sagaEmitter.emit(action); return result; }; }; }
// 發(fā)射器 export function emitter() { var subscribers = []; // 存儲需要訂閱的方法,并返回一個取消訂閱的方法 function subscribe(sub) { subscribers.push(sub); return function () { return remove(subscribers, sub); }; } // 這里執(zhí)行所有訂閱方法,我們點擊頁面上的按鈕,執(zhí)行dispatch的時候會執(zhí)行訂閱器里的函數(shù) function emit(item) { var arr = subscribers.slice(); for (var i = 0, len = arr.length; i < len; i++) { arr[i](item); } } }
dispatch的時候會執(zhí)行訂閱器里的函數(shù),那么訂閱器里的函數(shù)是什么呢,我們接著看第二步
初始化的時候調(diào)用一次saga,這個只調(diào)用一次。調(diào)用saga的目的把獲取管道中taker的方法push到訂閱函數(shù)中,同時獲得一個task。Task 接口指定了通過 fork,middleare.run 或 runSaga 運行 Saga 的結(jié)果。
方法 | 返回值 |
---|---|
task.isRunning() | 若任務還未返回或拋出了一個錯誤則為 true |
task.isCancelled() | 若任務已被取消則為 true |
task.result() | 任務的返回值。若任務仍在運行中則為 undefined |
task.error() | 任務拋出的錯誤。若任務仍在執(zhí)行中則為 undefined |
task.done | 一個 Promise,以下二者之一:1.以任務的返回值 2.resolve以任務拋出的錯誤 reject |
task.cancel() | 取消任務(如果任務仍在執(zhí)行中) |
export function runSaga(storeInterface, saga) { // iterator是封裝后的rootSaga var task = proc(iterator, subscribe, wrapSagaDispatch(dispatch), getState, context, { sagaMonitor: sagaMonitor, logger: logger, onError: onError }, effectId, saga.name); return task; }
下面看看執(zhí)行proc中發(fā)生了什么
上面的iterator是對rootSaga這個generator函數(shù)的封裝,在proc里redux-saga會執(zhí)行g(shù)en.next,就會執(zhí)行到我們的yield take("FETCH_USER");然后會返回value={TAKE:{pattern: "FETCH_USER"}},根據(jù)返回的值,判斷會執(zhí)行runTakeEffect()函數(shù),在這個函數(shù)里,會注冊一個taker,并把next添加到管道的taker中,到這里就結(jié)束了調(diào)用,并返回一個task。
export default function proc(iterator) { // 執(zhí)行這里會把獲取管道中taker的方法(chan.put)push到subscribers,所以上面第一步執(zhí)行訂閱中的方法實際上是執(zhí)行chan.put(input) var stdChannel = _stdChannel(subscribe); // 這里的task在第一次執(zhí)行的時候直接返回 var task = newTask(parentEffectId, name, iterator, cont); next(); function next(arg, isErr) { var result = void 0; // iterator是封裝后的rootSaga,這里執(zhí)行到的是yield take("FETCH_USER"); // 返回value={TAKE:{pattern: "FETCH_USER"}} result = iterator.next(arg); // 根據(jù)返回的value,這里會執(zhí)行 runEffect(result.value, parentEffectId, "", next); } }
// 這里cb是next function runTakeEffect(_ref2, cb) { var channel = _ref2.channel, pattern = _ref2.pattern, maybe = _ref2.maybe; channel = channel || stdChannel; var takeCb = function takeCb(inp) { return cb(inp); }; // 給管道注冊taker,把next函數(shù)放到takers數(shù)組中 channel.take(takeCb, matcher(pattern)); }
點擊獲取用戶信息的按鈕(onFetchUser={() => action("FETCH_USER")}),因為我們加入了saga中間件,讓我們發(fā)起store.dispatch({ type: FETCH_USER })的時候會處理異步操作,
下面是saga中間件的主要代碼
// next = store.dispatch 從redux中間件得出 return function (next) { return function (action) { // dispatch(action) var result = next(action); // hit reducers // 發(fā)射所有訂閱方法 sagaEmitter.emit(action); return result; }; };
下面我們來看看sagaEmitter.emit(action)會做什么
// 發(fā)射器 export function emitter() { ... // 發(fā)射所有訂閱方法 function emit(item) { var arr = subscribers.slice(); for (var i = 0, len = arr.length; i < len; i++) { arr[i](item); } } }
這里會遍歷訂閱函數(shù),并執(zhí)行訂閱函數(shù)里的方法。在初始化的是我們已經(jīng)把獲取管道中taker的方法push到訂閱函數(shù)了,
所以我們這里執(zhí)行的是獲取管道中的taker。
function put(input) { ... for (var i = 0; i < takers.length; i++) { var cb = takers[i]; if (!cb[MATCH] || cb[MATCH](input)) { takers.splice(i, 1); // 刪除已經(jīng)執(zhí)行過的taker return cb(input); // 這里cb實際上是next } } }
在初始化執(zhí)行yield take("FETCH_USER")的時候,已經(jīng)把gen.next放入到takers中,這里cb(input)實際上是執(zhí)行g(shù)en.next({ type: FETCH_USER }),
因為在初始化的時候gen函數(shù)已經(jīng)執(zhí)行了一次gen.next,現(xiàn)在執(zhí)行g(shù)en.next則為const { data } = yield call(axios.post, "http://rest.learncode.academy..."),同時把{ type: FETCH_USER }作為上一步的值傳入。執(zhí)行yeild call返回value
{CALL:{args: [url]}},根據(jù)返回值,
這里會執(zhí)行源碼中的
function runCallEffect(_ref4, effectId, cb) { const result = fn.apply(context, args); // 這里執(zhí)行結(jié)果是promise return resolvePromise(result, cb); }
function resolvePromise(promise, cb) { // cb是gen.next,這里把yield call的返回值傳遞給gen.next promise.then(cb, function (error) { return cb(error, true); }); }
接下來gen.next執(zhí)行到的是yield put({ type: "UPDATE_USER", payload: data }),執(zhí)行的返回值value
{PUT:{action:{payload:{id: "xx",type:"UPDATE_USER"}}}},根據(jù)返回值,
這里會執(zhí)行源碼中的
function runPutEffect(_ref3, cb) { var channel = _ref3.channel, action = _ref3.action; asap(function () { var result = (channel ? channel.put : dispatch)(action); // 實際上我們演示的這段代碼,這里會執(zhí)行dispatch(action) return cb(result); }); }
執(zhí)行dispatch(action)這里又會回到中間件中再次進入第三步的開始過程。并完成更新。
這次執(zhí)行到遍歷takers的地方,takers已經(jīng)為空數(shù)組,會直接return,至此完成了整個獲取接口到更新數(shù)據(jù)。
由于while(true)循環(huán),再次執(zhí)行yield take("FETCH_USER")。
下面附上兩張執(zhí)行流程圖
saga初始化
dispatch
這里只解釋了執(zhí)行流程和幾個api,更多的請參考文檔https://redux-saga-in-chinese...
本文通過簡單實現(xiàn)了幾個effect方法來地介紹了redux-saga的原理,要真正做到redux-saga的所有功能,只需要再添加一些細節(jié)就可以了
注:上面的源碼均為刪減版,可自行查看源碼。個人文章,轉(zhuǎn)載請注明出處。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/98089.html
摘要:原文地址前言筆者最近在做一些后臺項目,使用的是,其使用了處理異步數(shù)據(jù)流,本文將對的原理做一個簡單的解讀,并將實現(xiàn)一個簡易版的。函數(shù)的自動流程控制在中,是指一些長時操作,用函數(shù)表示。 原文地址 前言 筆者最近在做一些后臺項目,使用的是Ant Design Pro,其使用了redux-saga處理異步數(shù)據(jù)流,本文將對redux-saga的原理做一個簡單的解讀,并將實現(xiàn)一個簡易版的redux...
摘要:舉例來說一個異步的請求場景,可以如下實現(xiàn)任何異步的邏輯都可以,如等等也可以使用的和。實際上在中,一個就是一個函數(shù)。 書籍完整目錄 3.4 redux 異步 showImg(https://segmentfault.com/img/bVyou8); 在大多數(shù)的前端業(yè)務場景中,需要和后端產(chǎn)生異步交互,在本節(jié)中,將詳細講解 redux 中的異步方案以及一些異步第三方組件,內(nèi)容有: redu...
摘要:通過創(chuàng)建將所有的異步操作邏輯收集在一個地方集中處理,可以用來代替中間件。 redux-saga框架使用詳解及Demo教程 前面我們講解過redux框架和dva框架的基本使用,因為dva框架中effects模塊設計到了redux-saga中的知識點,可能有的同學們會用dva框架,但是對redux-saga又不是很熟悉,今天我們就來簡單的講解下saga框架的主要API和如何配合redux框...
摘要:相當于一個放置在與中的墊片。之所以稱之謂副作用呢,就是為了不讓觸發(fā)一個時,立即執(zhí)行。也就是在與之間做一個事情,比如異步獲取數(shù)據(jù)等。使用了中的功能,避免了像的回調(diào)地獄。把放入中最后再實現(xiàn)相就的即可在線示例推薦閱讀手稿 Redux-Saga redux-saga 是一個用于管理應用程序副作用(例如異步獲取數(shù)據(jù),訪問瀏覽器緩存等)的javascript庫,它的目標是讓副作用管理更容易,執(zhí)行更...
閱讀 955·2021-11-17 09:33
閱讀 415·2019-08-30 11:16
閱讀 2468·2019-08-29 16:05
閱讀 3351·2019-08-29 15:28
閱讀 1393·2019-08-29 11:29
閱讀 1947·2019-08-26 13:51
閱讀 3385·2019-08-26 11:55
閱讀 1203·2019-08-26 11:31