redux-saga源码学习

93 阅读3分钟

思维导图

image.png

loginSaga(demo)

// import { call, put, takeEvery, take, fork } from "redux-saga/effects";
import { call, put, take, fork } from "../saga-nut/effects";

import LoginService from "../service/login";
import {
  LOGIN_FAILURE,
  LOGIN_SAGA,
  LOGIN_SUCCESS,
  REQUEST,
} from "../store/const";

function* loginHandle(action) {
  yield put({ type: REQUEST });

  try {
    const res1 = yield call(LoginService.login, action.payload);
    const res2 = yield call(LoginService.getMoreUserInfo, res1);
    yield put({ type: LOGIN_SUCCESS, payload: res2 });
  } catch (err) {
    yield put({ type: LOGIN_FAILURE, payload: err });
  }
}

// watcher saga
function* loginSaga() {
  //   yield takeEvery(LOGIN_SAGA, loginHandle);

  while (true) {
    const action = yield take(LOGIN_SAGA);
    yield fork(loginHandle, action);
  }
}

export default loginSaga;

store

import { createStore, combineReducers, applyMiddleware } from "redux";
// import thunk from "redux-thunk";
// import createSagaMiddleware from "redux-saga";
import createSagaMiddleware from "../saga-nut";

// !1. 创建要运行的saga
import loginSaga from "../action/loginSaga";
import rootSaga from "../action/rootSaga";
import { loginReducer } from "./loginReducer";

// !2. 创建saga中间件
const sagaMiddleware = createSagaMiddleware();

const store = createStore(
  combineReducers({ user: loginReducer }),
  // !3. 把saga中间件与redux store链接
  applyMiddleware(sagaMiddleware)
);

// !4. 运行saga
sagaMiddleware.run(loginSaga, "xixi");

export default store;

入口

import { stdChannel } from "./channel";
import runSaga from "./runSaga";

export default function createSagaMiddleware() {
  let boundRunSaga;

  let channel = stdChannel();

  // redux-thunk logger promise
  function sagaMiddleware({ getState, dispatch }) {
    boundRunSaga = runSaga.bind(null, { channel, getState, dispatch });

    return (next) => (action) => {
      debugger;
      console.log("111");
      let result = next(action);
      channel.put(action);
      return result;
    };
  }

  sagaMiddleware.run = (...args) => boundRunSaga(...args);

  return sagaMiddleware;
}

  • 入口里主要是实现了一个中间件,与redux-thunk和redux-logger一样,createSagaMiddleware返回一个函数,且该返回的函数里需传入getState和dispatch
  • runSaga.bind主要是为了让saga获取store的参数,从而实现两者之间的通信
  • sagaMiddleware.run需用户手动执行相应的saga调用

runSaga

import proc from "./proc";

export default function runSaga(
  { channel, getState, dispatch },
  saga,
  ...args
) {
  // console.log(args, "000");
  debugger;
  const iterator = saga(...args);
  proc({ channel, getState, dispatch }, iterator);
}

这里主要是调用执行saga函数,然后进入到iterator的执行过程

iterator的主要处理过程

import { IO } from "./symbol";
import effectRunnerMap from "./effectRunnerMap";

//处理iterator
export default function proc(env, iterator) {
  next();

  function next(arg, isErr) {
    let result;
    if (isErr) {
      result = iterator.throw(arg);
    } else {
      result = iterator.next(arg);
    }
    // value done
    if (!result.done) {
      debugger;
      //
      digestEffect(result.value, next);
    }
  }

  function digestEffect(effect, cb) {
    let effectSettled;
    function currentCb(res, isErr) {
      if (effectSettled) {
        return; //防止死循环,如果副作用已经处理了,直接返回
      }
      effectSettled = true;
      cb(res, isErr);
    }
    runEffect(effect, currentCb);
  }

  function runEffect(effect, currentCb) {
    if (effect && effect[IO]) {
      const effectRunner = effectRunnerMap[effect.type];

      effectRunner(env, effect.payload, currentCb);
    } else {
      //如果没有副作用,那就直接执行下一个iterator
      currentCb();
    }
  }
}

这里特别需要注意genarator函数的特点,iterator.next()传入的参数是上一个yield的返回值,itarator.next()的返回值是一个{IO,type,payload}这样的对象。这里的实现是递归的起点

各种effects iterator的处理

import effectTypes from "./effectTypes";
import { promise } from "./is";
import proc from "./proc";

// todo channel
function runTakeEffect(env, { channel = env.channel, pattern }, cb) {
  const matcher = (input) => input.type === pattern;
  channel.take(cb, matcher);
}

function runPutEffect(env, { action }, cb) {
  const result = env.dispatch(action);
  cb(result);
}

function runCallEffect(env, { fn, args }, cb) {
  const result = fn.apply(null, args);
  if (promise(result)) {
    result.then((data) => cb(data)).catch((err) => cb(err, true));
    return;
  }
  cb(result);
}

function runForkEffect(env, { fn, args }, cb) {
  const taskIterator = fn.apply(null, args);
  proc(env, taskIterator);
  cb();
}

function runALlEffect(env, { effects }, cb) {
  let n = effects.length;
  for (let i = 0; i < n; i++) {
    proc(env, effects[i]);
  }
}

const effectRunnerMap = {
  [effectTypes.TAKE]: runTakeEffect,
  [effectTypes.PUT]: runPutEffect,
  [effectTypes.CALL]: runCallEffect,
  [effectTypes.FORK]: runForkEffect,
  [effectTypes.ALL]: runALlEffect,
};

export default effectRunnerMap;

  • 这里的cb回调都是去处理下一个iterator
  • proc的调用是因为执行了一个saga函数,组要重新进行一个iterator的处理过程
  • channel.take是对iterator处理开始的一个注册

effects

import effectTypes from "./effectTypes";
import { IO } from "./symbol";

function makeEffect(type, payload) {
  return {
    [IO]: IO,
    type,
    payload,
  };
}

export function take(pattern) {
  return makeEffect(effectTypes.TAKE, { pattern });
}

export function put(action) {
  return makeEffect(effectTypes.PUT, { action });
}

export function call(fn, ...args) {
  return makeEffect(effectTypes.CALL, { fn, args });
}

export function fork(fn, ...args) {
  return makeEffect(effectTypes.FORK, { fn, args });
}

export function all(effects) {
  return makeEffect(effectTypes.ALL, { effects });
}

这里每个effect返回的都是{IO,type,payload}的形式,与proc里iterator的处理相对应

export default {
  TAKE: "TAKE",
  PUT: "PUT",
  ALL: "ALL",
  RACE: "RACE",
  CALL: "CALL",
  CPS: "CPS",
  FORK: "FORK",
  JOIN: "JOIN",
  CANCEL: "CANCEL",
  SELECT: "SELECT",
  ACTION_CHANNEL: "ACTION_CHANNEL",
  CANCELLED: "CANCELLED",
  FLUSH: "FLUSH",
  GET_CONTEXT: "GET_CONTEXT",
  SET_CONTEXT: "SET_CONTEXT",
};

const createSymbol = (name) => `@@redux-saga/${name}`;

export const CANCEL = createSymbol("CANCEL_PROMISE");
export const CHANNEL_END_TYPE = createSymbol("CHANNEL_END");
export const IO = createSymbol("IO");
export const MATCH = createSymbol("MATCH");
export const MULTICAST = createSymbol("MULTICAST");
export const SAGA_ACTION = createSymbol("SAGA_ACTION");
export const SELF_CANCELLATION = createSymbol("SELF_CANCELLATION");
export const TASK = createSymbol("TASK");
export const TASK_CANCEL = createSymbol("TASK_CANCEL");
export const TERMINATE = createSymbol("TERMINATE");

export const SAGA_LOCATION = createSymbol("LOCATION");

export const func = (f) => typeof f === "function";
export const promise = (p) => p && func(p.then);

channel

import { MATCH } from "./symbol";

export function stdChannel() {
  const currentTakers = [];

  function take(cb, matcher) {
    cb[MATCH] = matcher;
    currentTakers.push(cb);
  }

  //   action
  function put(input) {
    const takers = currentTakers;
    for (let i = 0, len = takers.length; i < len; i++) {
      const taker = takers[i];
      if (taker[MATCH](input)) {
        taker(input);
      }
    }
  }

  return { take, put };
}

  • take主要是注册iterator处理过程的入口
  • 当用户执行dispatch时,put则是将take里注册的iterator拿出来进行下一步执行

总结

saga的核心原理就是帮用户自动执行generator函数,沿着这个思路其实就能把saga源码的实现串联起来