先上一张经典图为敬

image

redux-saga 是一个用于管理 redux 应用异步操作代替 redux-thunk 的中间件

  • 集中处理 redux 副作用问题。reducer 负责处理 action 的更新,saga 负责协调那些复杂或者异步的操作
  • 使用 generator 函数执行异步,generator 不是线程
  • watch/worker(监听->执行) 的工作形式

redux-saga 启动的任务可以在任何时候通过手动来取消,也可以把任务和其他的 Effects 放到 race 方法里以自动取消

createSagaMiddleware(...sagas:[])

createSagaMiddleware 的作用是创建一个 redux 中间件,并将 sagas 与 Redux store 建立链接

effect

只要是被 yield 的值,都可以被称为 effect。例如 yield 1 中,数字 1 就是 effect。 所有的 effect 都是通过 yield 语法传递给 effect-runner 的,effect-runner 处理该 effect 并决定什么时候恢复生成器。 redux-saga 里所有 effect 返回的值,都是一个带类型的纯 object 对象。 generator 的特点是执行到某一步时,可以把控制权交给外部代码,由外部代码拿到返回结果后,决定该怎么做。

put 作用和 redux 中的 dispatch 相同。

yield put({ type: 'CLICK_BTN' });

take 等待 redux dispatch 匹配某个 pattern 的 action 。

在这个例子中,先等待一个按钮点击的 action ,然后执行按钮点击的 saga:

while (true) {
  yield take('CLICK_BUTTON');
  yield fork(clickButtonSaga);
}

fork 当接收到 BEGIN_COUNT 的 action,则开始倒数,而接收到 STOP_COUNT 的 action, 则停止倒数。

function* count(number) {
  let currNum = number;

  while (currNum >= 0) {
    console.log(currNum--);
    yield delay(1000);
  }
}

function countSaga* () {
  while (true) {
    const { payload: number } = yield take(BEGIN_COUNT);
    const countTaskId = yield fork(count, number);

    yield take(STOP_TASK);
    yield cancel(countTaskId);
  }
}

call 有阻塞地调用 saga 或者返回 promise 的函数。

const project = yield call(fetch, { url: UrlMap.fetchProject });
const members = yield call(fetchMembers, project.id);

select 作用和 redux thunk 中的 getState 相同。

const id = yield select(state => state.id);

阻塞调用和无阻塞调用 其中 fork 是无阻塞型调用,call 是阻塞型调用。

channel

channel 是对事件源的抽象,作用是先注册一个 take 方法,当 put 触发时,执行一次 take 方法,然后销毁 take。

export function channel(buffer = buffers.expanding()) {
  let closed = false
  let takers = []

  function put(input) {
    if (closed) {
      return
    }
    if (takers.length === 0) {
      return buffer.put(input)
    }
    const cb = takers.shift()
    cb(input)
  }

  function take(cb) {
    if (closed && buffer.isEmpty()) {
      cb(END)
    } else if (!buffer.isEmpty()) {
      cb(buffer.take())
    } else {
      takers.push(cb)
      cb.cancel = () => {
        remove(takers, cb)
      }
    }
  }

  function flush(cb) {
    if (closed && buffer.isEmpty()) {
      cb(END)
      return
    }
    cb(buffer.flush())
  }

  function close() {
    if (closed) {
      return
    }

    closed = true

    const arr = takers
    takers = []

    for (let i = 0, len = arr.length; i < len; i++) {
      const taker = arr[i]
      taker(END)
    }
  }

  return {
    take,
    put,
    flush,
    close,
  }
}

当 put 触发时,如果 channel 里已经有注册了的 taker,taker 就会执行。

我们需要在 put 触发之前,先调用 channel 的 take 方法,注册实际要运行的方法。

const chan = channel();
btn.addEventListener('click', () => {
  const action =`action data${i++}`;
  chan.put(action);
}, false);

function* mainSaga() {
  const action = yield take();
  console.log(action);
}

这个 take 是 saga 里的一种 effect 类型。

task

task 是 generator 方法的执行环境,所有 saga 的 generator 方法都跑在 task 里。 简易实现如下:

function task(iterator) {
  const iter = iterator();
  function next(args) {
    const result = iter.next(args);
    if (!result.done) {
      const effect = result.value;
      if (effect.type === 'take') {
        runEffect(result.value, next);
      }
    }
  }
  next();
}

数据流转

image

// saga.js
// 它是一个 generator function
// fn 中同样包含了业务数据请求代码逻辑
function* fetchData(action) {
  const { payload: { someValue } } = action;
  try {
    const result = yield call(myAjaxLib.post, "/someEndpoint", { data: someValue });
    yield put({ type: "REQUEST_SUCCEEDED", payload: response });
  } catch (error) {
    yield put({ type: "REQUEST_FAILED", error: error });
  }
}

// watcher saga
// 监听每一次 dispatch(action)
// 如果 action.type === 'REQUEST',那么执行 fetchData
export function* watchFetchData() {
  yield takeEvery('REQUEST', fetchData);
}


// component.js
// View 层 dispatch(action) 触发异步请求
// 这里的 action 依然可以是一个 plain object
this.props.dispatch({
  type: 'REQUEST',
  payload: {
    someValue: { hello: 'saga' }
  }
});

对比 thunk

thunk 采用的是扩展 action 的方式:使得 redux 的 store 能 dispatch 的内容从普通对象扩展到函数

saga 采用的方案更接近于 redux 的全局思想 saga 采用发布订阅模式,需要一个经纪人 Broker,用于监听组件发出的 action,将监听到的 action1 转发给对应的订阅者,再由订阅者执行具体任务,任务执行完后,再发出另一个 action2 交由 reducer 修改 state

retry

import {call, put, select, takeLatest, retry} from "redux-saga/effects";

function* fetchFunc() {
  const {response} = yield call(apiRequest, {page: 1});
  if (response != null) {
    return response;
  } else {
    throw new Error("fetchOwnershipXPConfig: GET_OWNERSHIP_XP_CONFIG call failed.");
  }
}

const response = yield retry(3, 5 * 1000, fetchFunc);

results matching ""

    No results matching ""