React開發(fā)進階redux saga使用原理詳解
前言
工作中使用了redux-saga這個redux中間件,如果不明白內(nèi)部原理使用起來會讓人摸不著頭腦,閱讀源碼后特意對其原理做下總結。
redux的特點
- 一個標準、管理應用副作用的redux中間件
- 實現(xiàn)切面編程方式
- 聲明式的編寫方式
訂閱發(fā)布的設計模式
優(yōu)點:
- 把異步操作轉(zhuǎn)移到單獨 saga文件中,而不是糅雜在action或者component中;
- dispatch的參數(shù)保持為純粹的action而不是thunk function;
- 大量的saga輔助函數(shù)和effect創(chuàng)建器減少了開發(fā)者的開發(fā)成本;
- 靈活的串行或并行能夠?qū)崿F(xiàn)復雜的異步流程。
分析原理
先舉個實踐的例子
import { createStore, applyMiddleware } from 'redux';
import createSagaMiddleware from 'redux-saga';
import thunk from 'redux-thunk';
import { put, takeEvery, delay, call, select } from 'redux-saga/effects';
const reducer = (state = 0, action) => {
switch (action.type) {
case 'put':
return state + action.payload;
default:
return state;
}
};
const sagaMiddleware = createSagaMiddleware()
export const store = createStore(reducer, applyMiddleware(sagaMiddleware, thunk));
function* main() {
// 工具函數(shù) delay 阻塞1s
var start = Date.now();
yield delay(1000);
console.log(Date.now() - start);// 1秒多
// put 類似于 dispatch
yield put({ type: 'put' , payload:10});
// takeEvery 不阻塞程序
yield takeEvery('takeEvery111', function ({ type, payload }) {
console.log('takeEvery', type, payload); // yield put({ type: 'takeEvery111' , payload:10}); 觸發(fā)
});
// select 獲取state中的數(shù)據(jù)
const state = yield select((state) => state);
console.log(state); // 10
// call 阻塞程序
yield call(function* () { // 阻塞
yield delay(1000);
});
console.log(Date.now() - start);// 2秒多
yield put({ type: 'takeEvery111' , payload:10});
}
sagaMiddleware.run(main);
依次打印出如下結果:
1001
10
2004
takeEvery takeEvery111 10
1. 自動執(zhí)行Generator
從執(zhí)行結果來看,這個main函數(shù)能自動按順序執(zhí)行說明在redux-saga的程序代碼中有自動執(zhí)行gen的機制,其實源碼就是./internal/proc.js文件中,通過函數(shù)之間循環(huán)調(diào)用的方式執(zhí)行這個gen函數(shù)。
這樣的自動執(zhí)行機制在generator中是比較常見的,比如co模塊就具有這樣的功能,其實現(xiàn)巧妙卻不復雜,如下例子:
function makePromisify(source) {
if (source.then && typeof source.then === "function") return source
return Promise.resolve(source)
}
function run(generatorFunc) {
let it = generatorFunc()
let result = it.next()
return new Promise((resolve, reject) => {
const next = function (result) {
if (result.done) {
resolve(result.value)
}
//保證返回的是一個promise
result.value = makePromisify(result.value)
result.value.then(res => {
//將promise的返回值res傳入iterator迭代器的next方法中,作為yield后面表達式的返回值
//it.next將停止的yield繼續(xù)執(zhí)行到下一個yield,返回的result是一個value,done屬性組成的對象
let result = it.next(res)
//遞歸執(zhí)行next函數(shù)
next(result)
}).catch(err => {
reject(err)
})
}
next(result)
})
}
2. 發(fā)布訂閱模式
我們看到takeEvery是可以攔截到{type: 'takeEvery111'}這個action,說明在redux-saga內(nèi)部有類似發(fā)布訂閱on/trigger這樣的機制,通過閱讀源碼我們發(fā)現(xiàn),內(nèi)部通過channel這個東西來做發(fā)布訂閱的處理;在./internal/channel.js就有這樣的源碼:
put(input) {
const takers = (currentTakers = nextTakers)
for (let i = 0, len = takers.length; i < len; i++) {
const taker = takers[i]
// 如果take匹配到, 執(zhí)行它
if (taker[MATCH](input)) {
taker.cancel()
taker(input) // 發(fā)布
}
}
},
take(cb, matcher = matchers.wildcard) {
cb[MATCH] = matcher
ensureCanMutateNextTakers()
nextTakers.push(cb) // 訂閱
},
3. put, takeEvery, delay, call返回effect
put執(zhí)行并不是直接dispatch一個action,而是通過yield向redux-saga內(nèi)部傳遞參數(shù)(這個參數(shù)在redux-saga中叫effect),內(nèi)部根據(jù)這個參數(shù)決定具體的執(zhí)行內(nèi)容。
在源碼中put、fork、call 等是通過 makeEffect 創(chuàng)建了一系列 effect,這個 effect 是一個普通的 js 對象,上面掛載了一些相關的信息,并且把這個effect yield到內(nèi)部的runEffejct中,然后根據(jù)type字段決定真正需要執(zhí)行的程序過程。
const makeEffect = (type, payload) => ({
[IO]: true,
combinator: false,
type,
payload,
});
總結
redux-saga還有許多要探索的地方,比如channel、狀態(tài)機的應用、任務隊列,等。。。更多關于React進階redux saga原理的資料請關注腳本之家其它相關文章!
相關文章
React render核心階段深入探究穿插scheduler與reconciler
這篇文章主要介紹了React render核心階段穿插scheduler與reconciler,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習吧2022-11-11
react.js 獲取真實的DOM節(jié)點實例(必看)
下面小編就為大家?guī)硪黄猺eact.js 獲取真實的DOM節(jié)點實例(必看)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-04-04

