You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The upper part is the English version, and the lower part is the Chinese version, with the same content. If there are any wrong, or you have anything hard to understand, pls feel free to let me know.many thx.
Overview
From The data flow, How to use, Debug the source code, The principle analysis of the core API, How to expand your own saga, and Further analyze the source code of key functions to analyze redux-saga in detail from 6 angles
1.The data flow
2 How to use
//register saga
import { createStore, applyMiddleware } from 'redux';
import createSagaMiddleware from 'redux-saga';
import rootSaga from './sagas';
const sagaMiddleware = createSagaMiddleware();
const store = createStore(toDoApp, applyMiddleware(sagaMiddleware));
sagaMiddleware.run(rootSaga);
// every saga is a generator
import { all, call, put, takeEvery } from 'redux-saga/effects';
import { TODO_LIST } from '../actions';
//request data, then go to dispatch action by put
export function* fetchToDoList() {
const data = yield call(fetch, url);
yield put({ type: TODO_LIST, toDoList: data });
}
//Listen saga
export function* loadToDoList() {
yield takeEvery(LOAD_TODO_LIST, fetchToDoList);
}
//Listen to all saga in parallel
export default function* rootSaga() {
yield all([loadToDoList()]);
}
Note: redux-saga does not intercept action, but captures action by listening. Because it uses channel to cache effect runner, and continues to execute next(). So the original action will still go to reducer
3 Debug the source code
3.1 How to debug the source code
//1. Go into redux-saga/packages/redux-saga and execute the following command
yarn link
//2. Go into your project and execute the following command
yarn link redux-saga
Note: if you changed the source code, the corresponding code needs to be compiled. for example If the core source code is modified, redux-saga-effects.esm.js needs to be recompiled
Or directly modify the code of node_module and debug by the chrome devtool breakpoint
3.2 Debug the execution flow of saga in chrome devtool
3.3.1 Entrance: store.js
import createSagaMiddleware from 'redux-saga';
import rootSaga from './sagas';
const sagaMiddleware = createSagaMiddleware();
const store = createStore(reducer, applyMiddleware(sagaMiddleware));
sagaMiddleware.run(rootSaga);
Use a simple saga project example to debug, you can use any project that contains saga, because the debugging process is the same
3.3.2 Key functions
createSagaMiddleware
sagaMiddleware.run
rootSaga
channel(stdChannel)
The two core methods of channel are take, put. take is for inserting the taker into the queue nextTakers, put is for iterating the queue, and execute the matching taker
Taker is the next()
next
If the generator function does not end, digestEffect will be executed, and finalRunEffect will be executed in digestEffect, and finalRunEffect actually executes the runEffect function
proc
Encapsulates next and runEffect functions, and executes next proc source code
runEffect
If it is an effect, get the corresponding runEffect from effectRunnerMap and execute it, if not, execute next() directly
For example, effect.type is 'TAKE' or 'PUT', then execute runTakeEffect or runPutEffect
effectRunnerMap
effectRunnerMap source code
effectRunnerMap defines the runners corresponding to various side effects
For example, when effect.type is 'TAKE', runTakeEffect will be executed, and if it is 'PUT', runPutEffect will be executed. Other Effects are similar
The parameter cb in runTakeEffect and runPutEffect both refers to next.
runTakeEffect is to stuff next function into the channel queue. runPutEffect is to execute store.dispatch.
3.3.3 Summarize
createSagaMiddleware(sagaMiddlewareFactory) rewrites the dispatch method, which executes channel.put. channel.put will iterate the taker (next function in proc.js) queue and execute the matching taker
proc is executed in sagaMiddleware.run(rootSaga), next is executed in proc, and runEffect is executed in next. RunEffect judges that if it is an effect, execute the effectRunner matched by effectRunnerMap, if not, execute next.
If the judgment is 'TAKE', execute runTakeEffect. The function of runTakeEffect is to put taker(next) back into the channel queue. Wait for the next UI component to call store.dispatch and trigger channel.put, iterate the channel queue, and execute the matched taker(next)
The last next is the native store.dispatch
The core function of next is to execute the generator down, which is why 'TAKE' blocks there, because it ends after putting next into the channel queue
function runTakeEffect(env, {pattern}, cb) {
...
env.channel.take(cb, matcher);
}
Stuff cb(next) into the channel queue
It will be block, because cb(next) is not called after the queue is inserted, it means the generator is not continue to executed .
4.1.2 call
function runCallEffect(env, { fn, args }, cb) {
const result = fn.apply(null, args);
if (is.promise(result)) {
return result.then(data => cb(data)).catch(error => cb(error, true));
}
cb(result);
}
If call is a promise, it will block until Promise.resolve is invoked
4.1.2 put
function runPutEffect(env, { action }, cb) {
const result = env.dispatch(action);
cb(result);
}
Dispatch action to reducer
It won't be block, because executed cb(next)
4.1.2 select
function runSelectEffect(env, { selector, args }, cb) {
const state = selector(env.getState(), ...args)
cb(state)
}
How to use
let result = yield select(state => state.xxx);
//or
let result = yield select();
If there is no function passed in select, it will return all states by executing store.getState()
function takeEvery(pattern, saga) {
function* takeEveryHelper() {
while (true) {
yield take(pattern);
yield fork(saga);
}
}
return fork(takeEveryHelper);
}
souce code
Achieve the effect of each listening by an infinite loop generator
pattern is action type
takeEvery will trigger every time an action is listened, but takeLastest will only trigger the last time
5 How to extend your own saga
5.1 Add an effect runner
5.1.1 Adding types in effectTypes
5.1.2 Add the corresponding effect runner in effectRunnerMap
6 Further analysis of the source code of key functions
export function multicastChannel() {
let closed = false
//A task queue containing all callback functions (takers)
let currentTakers = []
let nextTakers = currentTakers
...
return {
//excute every taker
put(input) {
...
const takers = (currentTakers = nextTakers)
for (let i = 0, len = takers.length; i < len; i++) {
const taker = takers[i]
if (taker[MATCH](input)) {
taker.cancel()
taker(input)
}
}
},
take(cb, matcher = matchers.wildcard) {
...
cb[MATCH] = matcher
...
//add the cb(taker)
nextTakers.push(cb)
cb.cancel = once(() => {
ensureCanMutateNextTakers()
remove(nextTakers, cb)
})
},
close,
}
}
// stdChannel function is to execute in outside
export function stdChannel() {
const chan = multicastChannel()
const { put } = chan
chan.put = input => {
if (input[SAGA_ACTION]) {
put(input)
return
}
//asap is a scheduling function that only allows one task to execute at a time
asap(() => {
put(input)
})
}
return chan
}
The take of stdChannel is equivalent to listening, and put is equivalent to publishing
Simple version
function stdChannel(){
let currentTakers = [];
function take(taker,matcher){
taker['MATCH']=matcher;
currentTakers.push(taker);
}
function put(input){
for(let i=0;i<currentTakers.length;i++){
let taker = currentTakers[i];
let matcher = taker['MATCH'];
if(matcher(input)){
taker(input);
}
}
return {take,put};
}
}
let channel = stdChannel()
6.1.6 proc
Execute the next of the Generator object iterator once, and execute the corresponding type of effect runner according to the result(effect type) source code
export default function proc(env, iterator, parentContext, parentEffectId, meta, isRoot, cont) {
...
*
* receives either (command | effect result, false) or (any thrown thing, true)
*/
function next(arg, isErr) {
...
result = iterator.next(arg)
...
}
function runEffect(effect, effectId, currCb) {
...
} else if (effect && effect[IO]) {
const effectRunner = effectRunnerMap[effect.type]
effectRunner(env, effect.payload, currCb, executingContext)
} else {
// anything else returned as is
currCb(effect)
}
...
}
Simple version
export default function proc(env,iteraton){
function next(args){
let result;
//arg: genetor param //{done:false,value:{type:'TAKE'...}
result = iterator.next(args);
if(!result.done){
runEffect(result.value,next);
}
}
function runEffect(effect,next){
if(effect){
//different effect type to defferent effect runner
const effectRunner =effectRunnerMap[effect.type];
effectRunner(env,effect.payload,next);
}else{
next();
}
}
next();
}
export function multicastChannel() {
let closed = false
//包含所有回调函数(taker)的任务队列
let currentTakers = []
let nextTakers = currentTakers
...
return {
//执行每个回调函数
put(input) {
...
const takers = (currentTakers = nextTakers)
for (let i = 0, len = takers.length; i < len; i++) {
const taker = takers[i]
if (taker[MATCH](input)) {
taker.cancel()
taker(input)
}
}
},
take(cb, matcher = matchers.wildcard) {
...
cb[MATCH] = matcher
...
//添加每个回调函数
nextTakers.push(cb)
cb.cancel = once(() => {
ensureCanMutateNextTakers()
remove(nextTakers, cb)
})
},
close,
}
}
//外界执行的 stdChannel 函数
export function stdChannel() {
const chan = multicastChannel()
const { put } = chan
chan.put = input => {
if (input[SAGA_ACTION]) {
put(input)
return
}
//asap 是一个调度函数,每次只允许一个任务执行
asap(() => {
put(input)
})
}
return chan
}
stdChannel 的take相当于监听 put相当于发布
简化写法
function stdChannel(){
let currentTakers = [];
function take(taker,matcher){
taker['MATCH']=matcher;
currentTakers.push(taker);
}
function put(input){
for(let i=0;i<currentTakers.length;i++){
let taker = currentTakers[i];
let matcher = taker['MATCH'];
if(matcher(input)){
taker(input);
}
}
return {take,put};
}
}
let channel = stdChannel()
The upper part is the English version, and the lower part is the Chinese version, with the same content. If there are any wrong, or you have anything hard to understand, pls feel free to let me know.many thx.
Overview
1.The data flow
2 How to use
3 Debug the source code
3.1 How to debug the source code
3.2 Debug the execution flow of saga in chrome devtool
3.3.1 Entrance: store.js
3.3.2 Key functions
createSagaMiddleware

sagaMiddleware.run



rootSaga
channel(stdChannel)

next



proc

runEffect

effectRunnerMap



3.3.3 Summarize
4 Principle analysis of core API
4.1 Principle
4.1.1 take
4.1.2 call
4.1.2 put
4.1.2 select
How to use
4.1.2 fork
4.1.2 takeEvery & takeLastest
5 How to extend your own saga
5.1 Add an effect runner
5.1.1 Adding types in effectTypes
5.1.2 Add the corresponding effect runner in effectRunnerMap
6 Further analysis of the source code of key functions
redux-saga source code
6.1 execution flow
6.1.1 entrance in store
6.1.2 Find the source code corresponding to the entry
The entrance in the core compiled file

6.1.3 sagaMiddlewareFactory
source code
6.1.4 runSaga
source code
6.1.5 channel
source code
6.1.6 proc
Execute the next of the Generator object iterator once, and execute the corresponding type of effect runner according to the result(effect type)
source code
Simple version
6.1.7 effectRunnerMap
source code
The following is the Chinese version, the same content as above
Overview
1. 数据流
2 如何使用
3 调试源码
3.1 如何调试源码
3.2 在chrome devtool里调试saga的执行流程
3.3.1 入口: store.js
3.3.2 关键函数
createSagaMiddleware

sagaMiddleware.run



rootSaga
channel(stdChannel)

next



proc

runEffect

effectRunnerMap



3.3.3 总结
4 核心API的原理分析
4.1 原理
4.1.1 take
4.1.2 call
4.1.2 put
4.1.2 select
用法
4.1.2 fork
4.1.2 takeEvery & takeLastest
5 如何扩展自己的saga
5.1 新增一个effect runner
5.1.1 在effectTypes里新增类型
5.1.2 在effectRunnerMap里增加对应的effect runner
6 对关键函数的源码进一步分析
redux-saga源码
6.1 执行流程
6.1.1 store 里的入口
6.1.2 找到入口对应的源码
core编译文件里的入口

6.1.3 sagaMiddlewareFactory
源码
6.1.4 runSaga
源码
6.1.5 channel
源码
6.1.6 proc
执行一次Generator对象iterator的next,根据结果(effect type)执行对应类型的effect
源码
简化写法
6.1.7 effectRunnerMap
源码
The text was updated successfully, but these errors were encountered: