Redux-Saga API

326 阅读11分钟
  • 并不是完整的
  • 谨慎查看

Middleware API

createSagaMiddleware(options)

创建一个 Redux 中间件,将 Sagas 与 Redux Store 建立连接

  • options: Object - 传给 middleware 的option参数
    • context: Object - saga 的上下文初始值
    • sagaMonitor : SagaMonitor - 如果传入了 sagaMonitor,那么 middleware 在派发事件时会通知sagaMonitor
    • onError: (error: Error, { sagaStack: string }) - 如果传入了 onError,那么当 sagas中有未捕获的errors,middleware 会调用这个方法来处理。在追踪错误流时,这个方法很有用
    • effectMiddlewares : Function [] - 这个方法能阻拦任意的 effect,然后将该 effect 传给你自己调用或者传给别的middleware。可在这篇文章 中看到详细例子 
// main.js
// 接收一个初始值,export一个增强型的store

import { createStore, applyMiddleware} from 'redux';
import createSagaMiddleware from'redux-saga'
import reducer from'./reducer'
import sagas from'./sagas'
import initialState from './states'

// 创建一个middleware实例
export const sagaMiddleware = createSagaMiddleware()

// 连接store
// 注意:redux@>=3.1.0 的版本才支持把 middleware 作为 createStore 方法的最后一个参数
export const store = createStore(
    reducer,
    initialState,
    applyMiddleware(/* other middleware, */sagaMiddleware)
  )
}

// run saga
sagaMiddleware.run(sagas)

注意事项

sagas 中的每个 Generator 函数被调用时,都会被传入 Redux Store 的 getState 方法作为第一个参数。

sagas 中的每个函数都必须返回一个 Generator 。middleware 会迭代这个 Generator 并执行所有 yield 后的 Effect

在第一次迭代里,middleware 会调用 next() 方法以取得下一个 Effect。然后 middleware 会通过下面提到的 Effects API 来执行 yield 后的 Effect。

与此同时,Generator 会暂停,直到 Effect 执行结束。当接收到执行的结果,middleware 在 Generator 里接着调用 next(result),并将得到的结果作为参数传入。这个过程会一直重复,直到 Generator 正常结束或抛出错误。

如果执行引发了一个错误,就会调用 Generator 的 throw(error) 方法来代替。如果定义了一个 try/catch 包裹当前的 yield 指令,那么 catch 区块将被底层 Generator runtime 调用。

middleware.run(saga, ...args)

middleware 实例暴露了一个 run 方法。你可以用这个方法来执行 Sagas,于 applyMiddleware 阶段之后执行 Sagas。

  • saga: Function: 一个 Generator 函数
  • args: Array<any>: 提供给 saga 的参数 (除了 Store 的 getState 方法) 这个方法返回一个 Task 描述对象
import{ sagaMiddleware } from './main'

require.ensure(["dynamicSaga"],(require)=>{
  const dynamicSaga = require("dynamicSaga");
 const task = sagaMiddleware.run(dynamicSaga)
});

Effect Creators

take(pattern)

pattern

  • 为空或*,那么所有action都会匹配到
  • 为方法,则该方法为true时能匹配到
    (e.g. take(action => action.entities),entities为真时会被捕获到)

    Note: if the pattern function has toString defined on it, action.type will be tested against pattern.toString() instead. This is useful if you're using an action creator library like redux-act or redux-actions.
  • 为字符串,那么当 action.type === pattern 时匹配到
  • 为数组,action 会在 action.type === pattern 时匹配到

有一个特殊的action END. 如果dispatch END action, 那么所有Sagas blocked on a take Effect will be terminated regardless of the specified pattern. If the terminated Saga has still some forked tasks which are still running, it will wait for all the child tasks to terminate before terminating the Task

takeEvery(pattern, saga, ...args)

会捕获每一次匹配pattern的action。使用 take 和 fork 构建,可处理并发的action(即同时发起的)

const takeEvery = (patternOrChannel, saga, ...args) => fork(function*() {\
  while(true){
    const action = yield take(pattern)`
    yield fork(saga,...args.concat(action))`
  }
})

takeEvery 不会对多个任务的响应返回进行排序,并且也无法保证任务将会按照启动的顺序结束

takeLatest(pattern, saga, ...args)

每次发起一个 action 到 Store,并且这个 action 与 pattern 相匹配,那么 takeLatest 将会在后台启动一个新的 saga 任务。如果之前已经有一个 saga 任务启动了(当前 action 之前的最后发起的 action),并且这个任务在执行中,那这个任务将被取消。并抛出一个 SagaCancellationException 错误??

takeLatest 也是使用 take 和 fork 构建

const takeLatest = (patternOrChannel, saga, ...args) => fork(function*() {
  let lastTask
  while(true){
    const action = yield take(pattern)
    if(lastTask) {
      yield cancel(lastTask)// 如果任务已经终止,取消就是空操作`
    }
    lastTask = yield fork(saga,...args.concat(action))
  }
})

takeLeading(pattern, saga, ...args)

每次发起一个 action 到 Store,并且这个 action 与 pattern 相匹配,那么 takeLeading 将会在后台启动一个新的 saga 任务。一旦任务开始,在任务结束前 takeLeading 将不会捕获action
换句话说, takeLeading 只会在没有执行任务时,才会监听action

takeLeading 使用 take 和 call 构建

const takeLeading = (patternOrChannel, saga, ...args) => fork(function*() {
  while (true) {
    const action = yield take(patternOrChannel);
    yield call(saga, ...args.concat(action));
  }
})

put(action)

创建一条 Effect 描述信息,指示 middleware 发起一个 action 到 Store。 put 执行是异步的。即作为一个单独的 microtask。因此,如果saga任务区在执行或者前面有其他任务时,那么当前put不会立即执行

putResolve(action)

put 一样,但它是阻塞的

call(fn, ...args)

创建一条 Effect 描述信息,指示 middleware 调用 fn 函数并以 args 为参数。 fn 既可以是一个普通函数,也可以是一个 Generator 函数。

middleware 调用这个函数并检查它的结果。

如果结果是一个 Generator 对象,middleware 会执行它,就像在启动 Generator (startup Generators,启动时被传给 middleware)时做的。
如果有子级 Generator,那么在子级 Generator 正常结束前,父级 Generator 会暂停,这种情况下,父级 Generator 将会在子级 Generator 返回后继续执行,或者直到子级 Generator 被某些错误中止,
如果是这种情况,将在父级 Generator 中抛出一个错误。

如果结果是一个 Promise,middleware 会暂停直到这个 Promise 被 resolve,resolve 后 Generator 会继续执行。
或者直到 Promise 被 reject 了,如果是这种情况,将在 Generator 中抛出一个错误。

当 Generator 中抛出了一个错误,如果有一个 try/catch 包裹当前的 yield 指令,控制权将被转交给 catch
否则,Generator 会被错误中止,并且如果这个 Generator 被其他 Generator 调用了,错误将会传到调用的 Generator。

call([context, fn], ...args)

类似 call(fn, ...args),但支持为 fn 指定 this 上下文。用于调用对象的方法。

cps(fn, ...args)

创建一条 Effect 描述信息,指示 middleware 以 Node 风格调用 fn 函数。

fork(fn, ...args)

创建一条 Effect 描述信息,指示 middleware 以 无阻塞调用 方式执行 fn
在等待 fn 返回结果时,middleware 不会暂停 Generator。一旦fn被调用,Generator 立即恢复执行。

yield fork(fn ...args) 的结果是一个 Task 对象 —— 一个具备某些有用的方法和属性的对象。
所有fork出的任务,都和fork它们的上级任务有关联。当上级任务需要终止时,它会等待所有的fork任务终止后再返回???

fork任务里报了错误,却又没有try catch捕获它,那这个错误会冒泡到上级任务(也就是发起fork的任务)里,那么整棵上级任务树(包含上级任务和所有的fork任务)都会被cancelled

fork([context, fn], ...args)

类似 fork(fn, ...args),但支持为 fn 指定 this 上下文。用于调用对象的方法。

spawn(fn, ...args)

类似 fork(fn, ...args),但创建的是一个独立的任务。该任务从它的上级任务中独立出来,不属于上级任务的任务树的一部分,相当于一个顶层任务。也就是说,这个任务不会对上级任务有任何影响。

spawn([context, fn], ...args)

类似 spawn(fn, ...args),但支持为 fn 指定 this 上下文。用于调用对象的方法。

join(task)

创建一条 Effect 描述信息,指示 middleware 等待之前的 fork 任务返回结果。
task: Task - 之前的 fork 指令返回的 Task 对象

join([...tasks])

创建一条 Effect 描述信息,指示 middleware 等待之前的所有 fork 任务返回结果。

cancel(task)

创建一条 Effect 描述信息,指示 middleware 取消之前的 fork 任务。

取消执行中的 Generator,middleware 将会抛出一个 SagaCancellationException 的错误。

取消会向下传播。当取消 Generator 时,middleware 会同时取消当前 Effect(阻塞当前 Generator 的 Effect)。
如果当前 Effect 调用了另一个 Generator,那这个 Generator 也会被取消。

被取消的 Generator 可以捕获 SagaCancellationException 错误,以便在它结束前执行某些清理逻辑(例如,如果 Generator 正处于 AJAX 调用,清除 state 中的 isFetching 标识)。

注意,未被捕获的 SagaCancellationException 不会向上冒泡,如果 Generator 不处理取消异常,异常将不会冒泡至它的父级 Generator。

cancel 是一个无阻塞 Effect。也就是说,Generator 将在取消异常被抛出后立即恢复。 对于返回 Promise 结果的函数,你可以通过给 promise 附加一个 [CANCEL] 来插入自己的取消逻辑。

import { CANCEL } from 'redux-saga'
import { fork, cancel } from 'redux-saga/effects'

function myApi() {
  const promise = myXhr(...)

  promise[CANCEL] = () => myXhr.abort()
  return promise
}

function* mySaga() {

  const task = yield fork(myApi)

  // ... later
  // will call promise[CANCEL] on the result of myApi
  yield cancel(task)
}

cancel([...tasks])

创建一条 Effect 描述信息,指示 middleware 取消之前所有符合的的 fork 任务。

cancel()

创建一条 Effect 描述信息,指示 middleware 取消之前yield的任务(自己取消)

function* deleteRecord({ payload }) {
  try {
    const { confirm, deny } = yield call(prompt);
    if (confirm) {
      yield put(actions.deleteRecord.confirmed())
    }
    if (deny) {
      yield cancel()
    }
  } catch(e) {
    // handle failure
  } finally {
    if (yield cancelled()) {
      // shared cancellation logic
      yield put(actions.deleteRecord.cancel(payload))
    }
  }
}

select(selector, ...args)

如果 select 调用时参数为空(即 yield select()),那 effect 会取得整个的 state(和调用 getState() 的结果一样)

重要提醒:在发起 action 到 store 时,middleware 首先会转发 action 到 reducers 然后通知 Sagas。
这意味着,当你查询 Store 的 state,你获取的是 action 被处理之后的 state。

一般来说,顶级 Sagas (middleware 启动的 Sagas) 会被传入 Store 的 getState 方法,这个方法允许 Sagas 查询当前 Store 的 state。
但是 getState 函数必须在 嵌套 Sagas(其他 Sagas 启动的子级 Sagas)时被传来传去,以便访问 state。
所以我们不建议这样使用,而是创建选择器selector

// selectors.js
exportconst getCart = state => state.cart
// sagas.js
import{ getCart } from './selectors'
import{ select } from 'redux-saga/effects'

function* checkout(){
// 使用选择器查询 state
const cart = yield select(getCart)
// ... 调用某些 Api 然后发起一个 success/error action
}

actionChannel(pattern, [buffer])

创建一条 Effect 描述信息,指示 middleware 将匹配pattern的action加入到 channel 里。你还可以传入一个缓冲器来缓存这个队列

下面的例子创建了一个 channel ,来缓存所有的 USER_REQUEST action。注意到即使 call 中可以阻塞 Saga 任务,但在阻塞起来,所有action会被自动缓存。这样Saga就可以一个一个执行了

import { actionChannel, call } from 'redux-saga/effects'
import api from '...'

function* takeOneAtMost() {
  const chan = yield actionChannel('USER_REQUEST')
  while (true) {
    const {payload} = yield take(chan)
    yield call(api.getUser, payload)
  }
}

flush(channel)

创建一条 Effect 描述信息,指示 middleware 将 channel 里的缓存都刷新出来

cancelled()

创建一条 Effect 描述信息,指示 middleware 返回当前 Generator 是否 cancelled。通常在 finally 模块中使用这个 Effect 来执行特殊的 Cancel

function* saga() {
  try {
    // ...
  } finally {
    if (yield cancelled()) {
      // logic that should execute only on Cancellation
    }
    // logic that should execute in all situations (e.g. closing a channel)
  }
}

setContext(props)

创建一条 Effect 描述信息,指示 middleware 更新自身的 Context (上下文)。这个 Effect 是用来扩展 Context,而不是直接替换原来的。

getContext(props)

创建一条 Effect 描述信息,指示 middleware

delay(ms, [val])

返回一个描述性信息,阻塞执行 ms 毫秒,并且返回 val

throttle(ms, pattern, saga, ...args)

节流模式下,发起一个任务

throttle(ms, pattern, saga, ...args)

防抖模式下,发起一个任务

retry(maxTries, delay, fn, ...args)

创建一条 Effect 描述信息,指示 middleware 以 call 的方式调用 fn 函数,其参数是 args。如果失败,且累积失败次数小于 maxTries ,middleware 会在 delay 毫秒后,重新调用 fn

import { put, retry } from 'redux-saga/effects'
import { request } from 'some-api';

function* retrySaga(data) {
  try {
    const SECOND = 1000
    const response = yield retry(3, 10 * SECOND, request, data)
    yield put({ type: 'REQUEST_SUCCESS', payload: response })
  } catch(error) {
    yield put({ type: 'REQUEST_FAIL', payload: { error } })
  }
}

retry 是使用 dealay 和 call 构建的

Effect Combinators

race(effects)

创建一条 Effect 描述信息,指示 middleware 在多个 Effect 之间执行一个 race(类似 Promise.race([...]) 的行为)

import { take, call, race } from `redux-saga/effects`
import fetchUsers from './path/to/fetchUsers'

function* fetchUsersSaga() {
  const { response, cancel } = yield race({
    response: call(fetchUsers),
    cancel: take(CANCEL_FETCH)
  })
}

race([...effects])

和 race(effects) 一样,只不过是可以传入数组

import { take, call, race } from `redux-saga/effects`
import fetchUsers from './path/to/fetchUsers'

function* fetchUsersSaga() {
  const [response, cancel] = yield race([
    call(fetchUsers),
    take(CANCEL_FETCH)
  ])
}

all(effects)

创建一条 Effect 描述信息,指示 middleware 并行 执行多个 Effect,并等待所有 Effect 完成。 以下的例子并行执行了 2 个阻塞调用:

import { fetchCustomers, fetchProducts } from './path/to/api'
import { all, call } from `redux-saga/effects`

function* mySaga() {
  const { customers, products } = yield all({
    customers: call(fetchCustomers),
    products: call(fetchProducts)
  })
}

注意 并行执行多个 Effect 时,middleware 会暂停 Generator,直到以下情况之一:

  • 所有 Effect 成功完成:Generator 恢复并返回一个包含所有 Effect 结果的数组。
  • 在所有 Effect 完成之前,有一个 Effect 被 reject 了:Generator 抛出 reject 错误。

all([...effects])

和 all(effects) 一样,只不过是可以传入数组

import{ fetchCustomers, fetchProducts } from './path/to/api'
function* mySaga(){
const[customers, products]= yield [
    call(fetchCustomers),
    call(fetchProducts)
]
}

External API

runSaga(options, saga, ...args)

允许在 Redux middleware环境之外启动 saga。如果你想将 Saga 连接到外部input/output,而不是存储操作,则很有用 runSaga返回一个任务对象。就像从fork效果中返回的那个一样。