React開發(fā)進(jìn)階redux saga使用原理詳解
前言
工作中使用了redux-saga這個(gè)redux中間件,如果不明白內(nèi)部原理使用起來(lái)會(huì)讓人摸不著頭腦,閱讀源碼后特意對(duì)其原理做下總結(jié)。
redux的特點(diǎn)
- 一個(gè)標(biāo)準(zhǔn)、管理應(yīng)用副作用的redux中間件
- 實(shí)現(xiàn)切面編程方式
- 聲明式的編寫方式
訂閱發(fā)布的設(shè)計(jì)模式
優(yōu)點(diǎn):
- 把異步操作轉(zhuǎn)移到單獨(dú) saga文件中,而不是糅雜在action或者component中;
- dispatch的參數(shù)保持為純粹的action而不是thunk function;
- 大量的saga輔助函數(shù)和effect創(chuàng)建器減少了開發(fā)者的開發(fā)成本;
- 靈活的串行或并行能夠?qū)崿F(xiàn)復(fù)雜的異步流程。
分析原理
先舉個(gè)實(shí)踐的例子
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);
依次打印出如下結(jié)果:
1001
10
2004
takeEvery takeEvery111 10
1. 自動(dòng)執(zhí)行Generator
從執(zhí)行結(jié)果來(lái)看,這個(gè)main函數(shù)能自動(dòng)按順序執(zhí)行說(shuō)明在redux-saga的程序代碼中有自動(dòng)執(zhí)行g(shù)en的機(jī)制,其實(shí)源碼就是./internal/proc.js
文件中,通過(guò)函數(shù)之間循環(huán)調(diào)用的方式執(zhí)行這個(gè)gen函數(shù)。
這樣的自動(dòng)執(zhí)行機(jī)制在generator中是比較常見的,比如co模塊就具有這樣的功能,其實(shí)現(xiàn)巧妙卻不復(fù)雜,如下例子:
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) } //保證返回的是一個(gè)promise result.value = makePromisify(result.value) result.value.then(res => { //將promise的返回值res傳入iterator迭代器的next方法中,作為yield后面表達(dá)式的返回值 //it.next將停止的yield繼續(xù)執(zhí)行到下一個(gè)yield,返回的result是一個(gè)value,done屬性組成的對(duì)象 let result = it.next(res) //遞歸執(zhí)行next函數(shù) next(result) }).catch(err => { reject(err) }) } next(result) }) }
2. 發(fā)布訂閱模式
我們看到takeEvery是可以攔截到{type: 'takeEvery111'}
這個(gè)action,說(shuō)明在redux-saga
內(nèi)部有類似發(fā)布訂閱on/trigger
這樣的機(jī)制,通過(guò)閱讀源碼我們發(fā)現(xiàn),內(nèi)部通過(guò)channel
這個(gè)東西來(lái)做發(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一個(gè)action,而是通過(guò)yield向redux-saga內(nèi)部傳遞參數(shù)(這個(gè)參數(shù)在redux-saga中叫effect),內(nèi)部根據(jù)這個(gè)參數(shù)決定具體的執(zhí)行內(nèi)容。
在源碼中put、fork、call 等是通過(guò) makeEffect 創(chuàng)建了一系列 effect,這個(gè) effect 是一個(gè)普通的 js 對(duì)象,上面掛載了一些相關(guān)的信息,并且把這個(gè)effect yield到內(nèi)部的runEffejct中,然后根據(jù)type字段決定真正需要執(zhí)行的程序過(guò)程。
const makeEffect = (type, payload) => ({ [IO]: true, combinator: false, type, payload, });
總結(jié)
redux-saga還有許多要探索的地方,比如channel、狀態(tài)機(jī)的應(yīng)用、任務(wù)隊(duì)列,等。。。更多關(guān)于React進(jìn)階redux saga原理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
React render核心階段深入探究穿插scheduler與reconciler
這篇文章主要介紹了React render核心階段穿插scheduler與reconciler,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧2022-11-11react中ref獲取dom或者組件的實(shí)現(xiàn)方法
這篇文章主要介紹了react中ref獲取dom或者組件的實(shí)現(xiàn)方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-05-05react中setState的執(zhí)行機(jī)制詳解
setState() 的執(zhí)行機(jī)制包括狀態(tài)合并、批量更新、異步更新、虛擬 DOM 比較和渲染組件等步驟,這樣可以提高性能并優(yōu)化渲染過(guò)程,這篇文章主要介紹了react中的setState的執(zhí)行機(jī)制,需要的朋友可以參考下2023-10-10react.js 獲取真實(shí)的DOM節(jié)點(diǎn)實(shí)例(必看)
下面小編就為大家?guī)?lái)一篇react.js 獲取真實(shí)的DOM節(jié)點(diǎn)實(shí)例(必看)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-04-04React項(xiàng)目搭建與Echarts工具使用詳解
這篇文章主要介紹了React項(xiàng)目搭建與Echarts工具使用詳解,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-03-03