API 参考
中间件 API
createSagaMiddleware(options)
创建一个 Redux 中间件并将 Sagas 连接到 Redux Store
options: Object
- 传递给中间件的选项列表。当前支持的选项是context: Object
- saga 上下文的初始值。sagaMonitor
: SagaMonitor - 如果提供了 Saga Monitor,中间件将向监视器传递监视事件。onError: (error: Error, { sagaStack: string })
- 如果提供,中间件将使用来自 Sagas 的未捕获错误调用它。对于将未捕获的异常发送到错误跟踪服务很有用。effectMiddlewares
: Function [] - 允许您拦截任何效果,自行解析并传递给下一个中间件。有关详细示例,请参阅 此部分channel
: 如果提供,中间件将使用此通道而不是默认的stdChannel()
用于
take
和put
效果。
示例
下面我们将创建一个函数 configureStore
,它将使用新的方法 runSaga
增强 Store。然后,在我们的主模块中,我们将使用该方法启动应用程序的根 Saga。
configureStore.js
import createSagaMiddleware from 'redux-saga'
import reducer from './path/to/reducer'
export default function configureStore(initialState) {
// Note: passing middleware as the last argument to createStore requires redux@>=3.1.0
const sagaMiddleware = createSagaMiddleware()
return {
...createStore(reducer, initialState, applyMiddleware(/* other middleware, */sagaMiddleware)),
runSaga: sagaMiddleware.run
}
}
main.js
import configureStore from './configureStore'
import rootSaga from './sagas'
// ... other imports
const store = configureStore()
store.runSaga(rootSaga)
注意
有关 sagaMiddleware.run
方法的更多信息,请参见下文。
middleware.run(saga, ...args)
动态运行 saga
。可用于 仅在 applyMiddleware
阶段之后运行 Saga。
saga: Function
: 生成器函数args: Array<any>
: 要提供给saga
的参数
该方法返回一个 任务描述符。
注意
saga
必须是一个返回 生成器对象 的函数。然后,中间件将遍历生成器并执行所有生成的 Effect。
saga
也可以使用库提供的各种 Effect 启动其他 saga。下面描述的迭代过程也适用于所有子 saga。
在第一次迭代中,中间件调用 next()
方法以检索下一个 Effect。然后,中间件根据下面 Effects API 中指定的执行生成的 Effect。同时,生成器将挂起,直到效果执行终止。收到执行结果后,中间件将在生成器上调用 next(result)
,并将检索到的结果作为参数传递给它。重复此过程,直到生成器正常终止或抛出某些错误。
如果执行导致错误(如每个 Effect 创建者指定的),则将调用生成器的 throw(error)
方法。如果生成器函数在当前 yield 指令周围定义了 try/catch
,则底层生成器运行时将调用 catch
块。运行时还将调用任何相应的 finally 块。
如果 Saga 被取消(手动或使用提供的 Effect),则中间件将调用生成器的 return()
方法。这将导致生成器直接跳到 finally 块。
Effect 创建者
注意
- 以下每个函数都返回一个普通的 JavaScript 对象,并且不执行任何操作。
- 执行是在上述迭代过程中由中间件完成的。
- 中间件会检查每个 Effect 描述并执行相应的操作。
take(pattern)
创建一个 Effect 描述,指示中间件等待 Store 上的特定操作。生成器将暂停,直到匹配 pattern
的操作被分派。
yield take(pattern)
的结果是正在分派的 action 对象。
pattern
使用以下规则进行解释
如果
take
被调用时没有参数或为'*'
,则所有分派的 action 都将匹配(例如take()
将匹配所有 action)如果它是一个函数,则当
pattern(action)
为真时,action 将匹配(例如take(action => action.entities)
将匹配所有具有(真值)entities
字段的 action。)注意:如果模式函数在其上定义了
toString
,则action.type
将与pattern.toString()
进行测试。如果您使用的是像 redux-act 或 redux-actions 这样的 action 创建器库,这将很有用。如果它是一个字符串,则当
action.type === pattern
时,action 将匹配(例如take(INCREMENT_ASYNC)
)如果它是一个数组,则数组中的每个项目都将使用上述规则进行匹配,因此支持字符串和函数谓词的混合数组。最常见的用例是字符串数组,因此
action.type
将与数组中的所有项目进行匹配(例如take([INCREMENT, DECREMENT])
,这将匹配类型为INCREMENT
或DECREMENT
的 action)。
中间件提供了一个特殊的 action END
。如果您分派 END action,则所有在 take Effect 上阻塞的 Saga 将被终止,无论指定的模式如何。如果终止的 Saga 仍然有一些正在运行的分叉任务,它将等待所有子任务终止,然后再终止任务。
takeMaybe(pattern)
与 take(pattern)
相同,但不会在 END
action 上自动终止 Saga。相反,所有在 take Effect 上阻塞的 Saga 都将获得 END
对象。
备注
takeMaybe
的名字来源于 FP 类比 - 它就像不是返回类型为 ACTION
(自动处理)一样,我们可以拥有类型为 Maybe(ACTION)
,这样我们就可以处理两种情况
- 存在
Just(ACTION)
(我们有一个动作)的情况 NOTHING
的情况(通道已关闭*)。即我们需要某种方法来映射END
- 在内部,所有
dispatch
的动作都通过stdChannel
,当dispatch(END)
发生时,该通道会被关闭
take(channel)
创建一个 Effect 描述,指示中间件等待来自提供的通道的指定消息。如果通道已经关闭,则生成器将立即终止,遵循与 take(pattern)
相同的处理过程。
takeMaybe(channel)
与 take(channel)
相同,但不会在 END
动作上自动终止 Saga。相反,所有阻塞在 take Effect 上的 Saga 都将获得 END
对象。更多信息请参见 此处
takeEvery(pattern, saga, ...args)
在每个分派到 Store 的与 pattern
匹配的动作上生成一个 saga
。
pattern: String | Array | Function
- 有关更多信息,请参阅take(pattern)
的文档saga: Function
- 一个生成器函数args: Array<any>
- 要传递给已启动任务的参数。takeEvery
将把传入的动作添加到参数列表中(即,动作将是传递给saga
的最后一个参数)
示例
在以下示例中,我们创建了一个基本任务 fetchUser
。我们使用 takeEvery
在每个分派的 USER_REQUESTED
动作上启动一个新的 fetchUser
任务
import { takeEvery } from `redux-saga/effects`
function* fetchUser(action) {
...
}
function* watchFetchUser() {
yield takeEvery('USER_REQUESTED', fetchUser)
}
备注
takeEvery
是使用 take
和 fork
构建的高级 API。以下是使用低级 Effect 实现此帮助程序的方法
const takeEvery = (patternOrChannel, saga, ...args) => fork(function*() {
while (true) {
const action = yield take(patternOrChannel)
yield fork(saga, ...args.concat(action))
}
})
takeEvery
允许同时处理多个操作。在上面的示例中,当一个 USER_REQUESTED
操作被分发时,一个新的 fetchUser
任务会被启动,即使之前的 fetchUser
任务仍在进行中(例如,用户快速连续点击两次 Load User
按钮,第二次点击会分发一个 USER_REQUESTED
操作,而第一次点击触发的 fetchUser
任务尚未结束)。
takeEvery
不会处理任务的乱序响应。无法保证任务的结束顺序与它们启动的顺序相同。要处理乱序响应,您可以考虑使用下面的 takeLatest
。
takeEvery(channel, saga, ...args)
您也可以将一个通道作为参数传递,其行为与 takeEvery(pattern, saga, ...args) 相同。
takeLatest(pattern, saga, ...args)
在每个分发到 Store 并匹配 pattern
的操作上,都会创建一个 saga
任务。如果之前启动的 saga
任务仍在运行,则会自动取消该任务。
每次操作被分发到 Store 时,如果该操作匹配 pattern
,takeLatest
会在后台启动一个新的 saga
任务。如果之前(在实际操作之前分发的最后一个操作上)启动了一个 saga
任务,并且该任务仍在运行,则该任务会被取消。
pattern: String | Array | Function
- 有关更多信息,请参阅take(pattern)
的文档saga: Function
- 一个生成器函数args: Array<any>
- 传递给启动的任务的参数。takeLatest
会将传入的操作添加到参数列表中(即,操作将是传递给saga
的最后一个参数)。
示例
在以下示例中,我们创建了一个基本的 fetchUser
任务。我们使用 takeLatest
在每个分发的 USER_REQUESTED
操作上启动一个新的 fetchUser
任务。由于 takeLatest
会取消之前启动的任何挂起任务,因此我们可以确保,如果用户快速触发多个连续的 USER_REQUESTED
操作,我们只会完成最新的操作。
import { takeLatest } from `redux-saga/effects`
function* fetchUser(action) {
...
}
function* watchLastFetchUser() {
yield takeLatest('USER_REQUESTED', fetchUser)
}
备注
takeLatest
是使用 take
和 fork
构建的高级 API。以下是使用低级效果实现该助手的方法。
const takeLatest = (patternOrChannel, saga, ...args) => fork(function*() {
let lastTask
while (true) {
const action = yield take(patternOrChannel)
if (lastTask) {
yield cancel(lastTask) // cancel is no-op if the task has already terminated
}
lastTask = yield fork(saga, ...args.concat(action))
}
})
takeLatest(channel, saga, ...args)
您也可以传入一个通道作为参数,其行为与 takeLatest(pattern, saga, ...args) 相同。
takeLeading(pattern, saga, ...args)
在每次分派到与pattern
匹配的 Store 的动作时,都会生成一个saga
。在生成任务一次后,它会阻塞,直到生成的 saga 完成,然后开始再次监听pattern
。
简而言之,takeLeading
在不运行 saga 时监听动作。
pattern: String | Array | Function
- 有关更多信息,请参阅take(pattern)
的文档saga: Function
- 一个生成器函数args: Array<any>
- 传递给已启动任务的参数。takeLeading
将传入的动作添加到参数列表中(即,动作将是传递给saga
的最后一个参数)。
示例
在以下示例中,我们创建了一个基本任务fetchUser
。我们使用takeLeading
在每次分派USER_REQUESTED
动作时启动一个新的fetchUser
任务。由于takeLeading
在启动后会忽略任何新的传入任务,因此我们确保如果用户快速触发多个连续的USER_REQUESTED
动作,我们只会继续运行领先的动作。
import { takeLeading } from `redux-saga/effects`
function* fetchUser(action) {
...
}
function* watchLastFetchUser() {
yield takeLeading('USER_REQUESTED', fetchUser)
}
备注
takeLeading
是使用take
和call
构建的高级 API。以下是使用低级效果实现帮助程序的方法。
const takeLeading = (patternOrChannel, saga, ...args) => fork(function*() {
while (true) {
const action = yield take(patternOrChannel);
yield call(saga, ...args.concat(action));
}
})
takeLeading(channel, saga, ...args)
您也可以传入一个通道作为参数,其行为与 takeLeading(pattern, saga, ...args) 相同。
put(action)
创建一个效果描述,指示中间件将调度动作到存储。此调度可能不会立即发生,因为其他任务可能在 saga 任务队列中排队或仍在进行中。
但是,您可以期望存储在当前堆栈帧中更新(即在yield put(action)
之后的下一行代码中),除非您有其他具有异步流程的 Redux 中间件,这些中间件会延迟动作的传播。
下游错误(例如来自 reducer 的错误)将被冒泡。
action: Object
- 有关完整信息,请参阅 Reduxdispatch
文档
putResolve(action)
就像 put
一样,但效果是阻塞的(如果从dispatch
返回 promise,它将等待其解析),并将从下游冒泡错误。
action: Object
- 查看 Reduxdispatch
文档以获取完整信息
put(channel, action)
创建一个 Effect 描述,指示中间件将一个 action 放入提供的 channel 中。
channel: Channel
- 一个Channel
对象。action: Object
- 查看 Reduxdispatch
文档以获取完整信息
如果 put 没有被缓冲,而是被 taker 立即消费,则此 effect 是阻塞的。如果在任何这些 taker 中抛出错误,它将冒泡回 saga。
call(fn, ...args)
创建一个 Effect 描述,指示中间件使用 args
作为参数调用函数 fn
。
fn: Function
- 一个 Generator 函数,或一个正常函数,它要么返回一个 Promise 作为结果,要么返回任何其他值。args: Array<any>
- 一个要作为参数传递给fn
的值数组。
备注
fn
可以是正常函数或 Generator 函数。
中间件调用该函数并检查其结果。
如果结果是一个 Iterator 对象,中间件将运行该 Generator 函数,就像它在启动时运行启动 Generator(传递给启动时的中间件)一样。父 Generator 将被挂起,直到子 Generator 正常终止,在这种情况下,父 Generator 将使用子 Generator 返回的值恢复。或者,直到子 Generator 以某种错误中止,在这种情况下,将在父 Generator 中抛出错误。
如果 fn
是一个正常函数并返回一个 Promise,中间件将挂起 Generator 直到 Promise 解决。Promise 解决后,Generator 将使用解决的值恢复,或者如果 Promise 被拒绝,则在 Generator 中抛出错误。
如果结果不是 Iterator 对象也不是 Promise,中间件将立即将该值返回给 saga,以便它可以同步地恢复执行。
当在 Generator 中抛出错误时,如果它有一个 try/catch
块围绕当前的 yield
指令,则控制将传递给 catch
块。否则,Generator 将以抛出的错误中止,如果此 Generator 被另一个 Generator 调用,则错误将传播到调用 Generator。
call([context, fn], ...args)
与 call(fn, ...args)
相同,但支持将 this
上下文传递给 fn
。这对于调用对象方法很有用。
call([context, fnName], ...args)
与 call([context, fn], ...args)
相同,但支持将 fn
作为字符串传递。这对于调用对象的函数很有用,例如 yield call([localStorage, 'getItem'], 'redux-saga')
call({context, fn}, ...args)
与 call([context, fn], ...args)
相同,但支持将 context
和 fn
作为对象的属性传递,例如 yield call({context: localStorage, fn: localStorage.getItem}, 'redux-saga')
。fn
可以是字符串或函数。
apply(context, fn, [args])
call([context, fn], ...args)
的别名。
cps(fn, ...args)
创建一个 Effect 描述,指示中间件将 fn
作为 Node 风格函数调用。
fn: Function
- Node 风格函数。例如,一个函数除了接受参数之外,还接受一个额外的回调函数,该回调函数在fn
终止时由fn
调用。回调函数接受两个参数,第一个参数用于报告错误,第二个参数用于报告成功的结果args: Array<any>
- 一个数组,将作为fn
的参数传递
备注
中间件将执行 fn(...arg, cb)
调用。cb
是中间件传递给 fn
的回调函数。如果 fn
正常终止,它必须调用 cb(null, result)
来通知中间件成功的结果。如果 fn
遇到错误,则它必须调用 cb(error)
来通知中间件发生了错误。
中间件将一直处于挂起状态,直到 fn
终止。
cps([context, fn], ...args)
支持将 this
上下文传递给 fn
(对象方法调用)
cps({context, fn}, ...args)
与 cps([context, fn], ...args)
相同,但支持将 context
和 fn
作为对象的属性传递。fn
可以是字符串或函数。
fork(fn, ...args)
创建一个 Effect 描述,指示中间件对 fn
执行非阻塞调用
参数
fn: Function
- 生成器函数,或返回 Promise 作为结果的普通函数args: Array<any>
- 一个要作为参数传递给fn
的值数组。
返回一个 Task 对象。
注意
fork
与 call
一样,可用于调用普通函数和生成器函数。但是,调用是非阻塞的,中间件不会在等待 fn
的结果时挂起生成器。相反,一旦 fn
被调用,生成器就会立即恢复。
fork
与 race
一起,是管理 Saga 之间并发性的核心 Effect。
yield fork(fn ...args)
的结果是一个 Task 对象。一个包含一些有用方法和属性的对象。
所有分叉的任务都附加到它们的父级。当父级终止其自身指令主体执行时,它将等待所有分叉的任务终止,然后返回。
错误传播
子任务的错误会自动冒泡到它们的父级。如果任何分叉的任务引发未捕获的错误,则父任务将使用子错误中止,并且整个父级的执行树(即分叉的任务 + 如果仍在运行,则由父级主体表示的主任务)将被取消。
取消分叉的任务将自动取消所有仍在执行的分叉任务。它还会取消当前被取消的任务阻塞的 Effect(如果有)。
如果分叉的任务同步失败(即:在执行任何异步操作之前立即失败),则不会返回任何 Task,而是父级将尽快中止(因为父级和子级并行执行,父级将在注意到子级失败后立即中止)。
要创建分离的分叉,请使用 spawn
代替。
fork([context, fn], ...args)
支持使用 this
上下文调用分叉函数
fork({context, fn}, ...args)
与 fork([context, fn], ...args)
相同,但支持将 context
和 fn
作为对象的属性传递。fn
可以是字符串或函数。
spawn(fn, ...args)
与 fork(fn, ...args)
相同,但创建了一个分离的任务。分离的任务独立于其父任务,并像顶级任务一样运行。父任务不会等待分离的任务终止才返回,并且所有可能影响父任务或分离任务的事件都是完全独立的(错误、取消)。
spawn([context, fn], ...args)
支持使用 this
上下文生成函数。
join(task)
创建一个 Effect 描述,指示中间件等待之前分叉的任务的结果。
task: Task
- 由之前的fork
返回的 Task 对象。
备注
join
将解析为与已加入任务相同的结果(成功或错误)。如果已加入的任务被取消,取消也将传播到执行 join effect 的 Saga。类似地,所有可能调用这些 joiners 的调用者也将被取消。
join([...tasks])
创建一个 Effect 描述,指示中间件等待之前分叉的任务的结果。
tasks: Array<Task>
- Task 是由之前的fork
返回的对象。
备注
它将任务数组包装在 join effects 中,大致等同于 yield tasks.map(t => join(t))
。
cancel(task)
创建一个 Effect 描述,指示中间件取消之前分叉的任务。
task: Task
- 由之前的fork
返回的 Task 对象。
备注
要取消正在运行的任务,中间件将调用底层 Generator 对象的 return
。这将取消任务中的当前 Effect 并跳转到 finally 块(如果定义)。
在 finally 块中,您可以执行任何清理逻辑或调度一些操作以使存储保持一致状态(例如,当 ajax 请求被取消时,将 spinner 的状态重置为 false)。您可以在 finally 块中通过发出 yield cancelled()
来检查 Saga 是否被取消。
取消向下传播到子 saga。取消任务时,中间件也会取消当前 Effect(任务当前被阻塞的地方)。如果当前 Effect 是对另一个 Saga 的调用,它也将被取消。取消 Saga 时,所有附加分叉(使用 yield fork()
分叉的 saga)将被取消。这意味着取消实际上会影响属于取消任务的整个执行树。
cancel
是一个非阻塞 Effect。即执行它的 Saga 将在执行取消后立即恢复。
对于返回 Promise 结果的函数,您可以通过将 [CANCEL]
附加到 Promise 来插入自己的取消逻辑。
以下示例展示了如何将取消逻辑附加到 Promise 结果
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)
}
redux-saga 将自动使用 abort
方法取消 jqXHR 对象。
cancel([...tasks])
创建一个 Effect 描述,指示中间件取消之前分叉的任务。
tasks: Array<Task>
- Task 是由之前的fork
返回的对象。
注意
它将任务数组包装在 取消效果 中,大致相当于 yield tasks.map(t => cancel(t))
。
cancel()
创建一个 Effect 描述,指示中间件取消它被 yield 的任务(自取消)。它允许在 finally
块中重用类似析构的逻辑,用于外部(cancel(task)
)和自身(cancel()
)取消。
示例
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)
创建一个效果,指示中间件在当前 Store 的状态上调用提供的选择器(即返回 selector(getState(), ...args)
的结果)。
selector: Function
- 一个函数(state, ...args) => args
。它接受当前状态和可选的一些参数,并返回当前 Store 状态的一部分args: Array<any>
- 除getState
之外,传递给选择器的可选参数。
如果 select
在没有参数的情况下被调用(即 yield select()
),则效果将使用整个状态(与 getState()
调用相同的结果)解析。
需要注意的是,当一个动作被分派到 store 时,中间件首先将动作转发给 reducer,然后通知 Saga。这意味着当您查询 Store 的状态时,您获得的是动作应用后的状态。但是,这种行为只有在所有后续中间件同步调用
next(action)
时才保证。如果任何后续中间件异步调用next(action)
(这很不寻常,但可能),那么 saga 将从动作应用前的状态获取状态。因此,建议您查看每个后续中间件的源代码,以确保它同步调用next(action)
,或者确保 redux-saga 是调用链中的最后一个中间件。
备注
理想情况下,Saga 应该自治,不应该依赖于 Store 的状态。这样可以方便地修改状态实现,而不会影响 Saga 代码。Saga 应该尽可能地只依赖于它自己的内部控制状态。但有时,人们可能会发现让 Saga 查询状态比自己维护所需数据更方便(例如,当 Saga 复制调用某些 reducer 的逻辑来计算 Store 已经计算过的状态时)。
例如,假设我们在应用程序中具有以下状态形状
state = {
cart: {...}
}
我们可以创建一个选择器,即一个知道如何从状态中提取cart
数据的函数
./selectors
export const getCart = state => state.cart
然后,我们可以使用 Saga 中的select
效果使用该选择器
./sagas.js
import { take, fork, select } from 'redux-saga/effects'
import { getCart } from './selectors'
function* checkout() {
// query the state using the exported selector
const cart = yield select(getCart)
// ... call some API endpoint then dispatch a success/error action
}
export default function* rootSaga() {
while (true) {
yield take('CHECKOUT_REQUEST')
yield fork(checkout)
}
}
checkout
可以通过使用select(getCart)
直接获取所需的信息。Saga 仅与getCart
选择器耦合。如果我们有许多需要访问cart
切片的 Saga(或 React 组件),它们都将与同一个函数getCart
耦合。如果我们现在更改状态形状,我们只需要更新getCart
。
actionChannel(pattern, [buffer])
创建一个效果,指示中间件使用事件通道对匹配pattern
的操作进行排队。可以选择提供一个缓冲区来控制排队操作的缓冲。
pattern:
- 请参阅take(pattern)
的 APIbuffer: Buffer
- 一个Buffer对象
示例
以下代码创建一个通道来缓冲所有USER_REQUEST
操作。请注意,即使 Saga 可能会在call
效果上被阻塞。在它被阻塞时到达的所有操作都会自动被缓冲。这会导致 Saga 每次执行一个 API 调用
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)
创建一个效果,指示中间件从通道中刷新所有缓冲项。刷新的项将返回到 saga,因此可以根据需要使用它们。
channel: Channel
- 一个Channel
对象。
示例
function* saga() {
const chan = yield actionChannel('ACTION')
try {
while (true) {
const action = yield take(chan)
// ...
}
} finally {
const actions = yield flush(chan)
// ...
}
}
cancelled()
创建一个指示中间件返回此生成器是否已取消的效果。通常,您在 finally 块中使用此 Effect 来运行与取消相关的代码。
示例
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。此 Effect 扩展了 saga 的上下文,而不是替换它。
getContext(prop)
创建一个指示中间件返回 saga 上下文特定属性的 Effect。
delay(ms, [val])
返回一个 Effect 描述符,用于阻塞执行 ms
毫秒并返回 val
值。
throttle(ms, pattern, saga, ...args)
在调度到 Store 并匹配 pattern
的操作上生成一个 saga
。在生成任务后,它仍然接受进入底层 buffer
的传入操作,最多保留 1 个(最新的一个),但在同一时间,在 ms
毫秒内暂停生成新任务(因此得名 - throttle
)。这样做是为了在处理任务时忽略一段时间内的传入操作。
ms: Number
- 操作开始处理后忽略操作的时间窗口长度(以毫秒为单位)。pattern: String | Array | Function
- 有关更多信息,请参阅take(pattern)
的文档saga: Function
- 一个生成器函数args: Array<any>
- 要传递给已启动任务的参数。throttle
会将传入的操作添加到参数列表中(即,操作将是提供给saga
的最后一个参数)。
示例
在以下示例中,我们创建了一个基本任务 fetchAutocomplete
。我们使用 throttle
在调度 FETCH_AUTOCOMPLETE
操作时启动一个新的 fetchAutocomplete
任务。但是,由于 throttle
在一段时间内忽略连续的 FETCH_AUTOCOMPLETE
,因此我们确保用户不会向我们的服务器发送大量请求。
import { call, put, throttle } from `redux-saga/effects`
function* fetchAutocomplete(action) {
const autocompleteProposals = yield call(Api.fetchAutocomplete, action.text)
yield put({type: 'FETCHED_AUTOCOMPLETE_PROPOSALS', proposals: autocompleteProposals})
}
function* throttleAutocomplete() {
yield throttle(1000, 'FETCH_AUTOCOMPLETE', fetchAutocomplete)
}
说明
throttle
是使用 take
、fork
和 actionChannel
构建的高级 API。以下是使用低级 Effect 实现此帮助程序的方法。
const throttle = (ms, pattern, task, ...args) => fork(function*() {
const throttleChannel = yield actionChannel(pattern, buffers.sliding(1))
while (true) {
const action = yield take(throttleChannel)
yield fork(task, ...args, action)
yield delay(ms)
}
})
throttle(ms, channel, saga, ...args)
您也可以使用通道作为参数,其行为与throttle(ms, pattern, saga, ..args)
相同。
debounce(ms, pattern, saga, ...args)
在与pattern
匹配的 Store 中分派的 action 上生成一个saga
。当它停止接收pattern
action ms
毫秒后,Saga 将被调用。这样做是为了防止在 action 解决之前调用 saga。
ms: Number
- 定义自上次触发pattern
action 以来调用saga
应该经过的毫秒数。pattern: String | Array | Function
- 有关更多信息,请参阅take(pattern)
的文档saga: Function
- 一个生成器函数args: Array<any>
- 传递给已启动任务的参数。debounce
将把传入的 action 添加到参数列表中(即 action 将是传递给saga
的最后一个参数)。
示例
在以下示例中,我们创建了一个基本任务fetchAutocomplete
。我们使用debounce
来延迟调用fetchAutocomplete
saga,直到我们停止接收任何FETCH_AUTOCOMPLETE
事件至少1000
毫秒。
import { call, put, debounce } from `redux-saga/effects`
function* fetchAutocomplete(action) {
const autocompleteProposals = yield call(Api.fetchAutocomplete, action.text)
yield put({type: 'FETCHED_AUTOCOMPLETE_PROPOSALS', proposals: autocompleteProposals})
}
function* debounceAutocomplete() {
yield debounce(1000, 'FETCH_AUTOCOMPLETE', fetchAutocomplete)
}
备注
debounce
是使用take
、delay
、race
和fork
构建的高级 API。以下是使用低级 Effects 如何实现该助手。
const debounce = (ms, pattern, task, ...args) => fork(function*() {
while (true) {
let action = yield take(pattern)
while (true) {
const { debounced, latestAction } = yield race({
debounced: delay(ms),
latestAction: take(pattern)
})
if (debounced) {
yield fork(task, ...args, action)
break
}
action = latestAction
}
}
})
debounce(ms, channel, saga, ...args)
您也可以使用通道作为参数,其行为与debounce(ms, pattern, saga, ..args)
相同。
retry(maxTries, delay, fn, ...args)
创建一个 Effect 描述,指示中间件使用args
作为参数调用函数fn
。如果失败,将在delay
毫秒后尝试再次调用,如果尝试次数 < maxTries
。
maxTries: Number
- 最大调用次数。delay: Number
-fn
调用之间的时间窗口长度(以毫秒为单位)。fn: Function
- 一个 Generator 函数,或返回 Promise 作为结果的普通函数,或任何其他值。args: Array<any>
- 一个要作为参数传递给fn
的值数组。
示例
在以下示例中,我们创建了一个基本任务retrySaga
。我们使用retry
来尝试以 10 秒的间隔获取我们的 API 3 次。如果request
第一次失败,那么retry
将再次调用request
,直到调用次数小于 3。
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
是使用 delay
和 call
构建的高级 API。 以下是使用低级 Effects 实现此帮助程序的方法
Effect 组合器
race(effects)
创建一个 Effect 描述,指示中间件在多个 Effects 之间运行一场竞赛(这类似于 Promise.race([...])
的行为方式)。
effects: Object
- 一个字典对象,形式为 {label: effect, ...}
示例
以下示例在两个效果之间进行竞赛
- 对返回 Promise 的函数
fetchUsers
的调用 - 最终可能在 Store 上分派的
CANCEL_FETCH
操作
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)
})
}
如果 call(fetchUsers)
首先解析,则 race
的结果将是一个带有一个键值对象的 {response: result}
,其中 result
是 fetchUsers
的解析结果。
如果 call(fetchUsers)
首先拒绝,则 race
会抛出拒绝原因。
如果在 fetchUsers
完成之前,在 Store 上分派了类型为 CANCEL_FETCH
的操作,则结果将是一个带有一个键值对象的 {cancel: action}
,其中 action 是分派的 action。
备注
在解析 race
时,中间件会自动取消所有失败的 Effects。
race([...effects]) (使用数组)
与 race(effects)
相同,但允许您传入一个 effects 数组。
示例
以下示例在两个效果之间进行竞赛
- 对返回 Promise 的函数
fetchUsers
的调用 - 最终可能在 Store 上分派的
CANCEL_FETCH
操作
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)
])
}
如果 call(fetchUsers)
首先解析,则 response
将是 fetchUsers
的结果,而 cancel
将是 undefined
。
如果 call(fetchUsers)
首先拒绝,则 race
会抛出拒绝原因。
如果在 fetchUsers
完成之前,在 Store 上分发了类型为 CANCEL_FETCH
的动作,则 response
将为 undefined
,而 cancel
将为分发的动作。
all([...effects]) - 并行效果
创建一个 Effect 描述,指示中间件并行运行多个 Effect 并等待它们全部完成。它与标准 Promise#all
的 API 非常相似。
示例
以下示例并行运行两个阻塞调用
import { fetchCustomers, fetchProducts } from './path/to/api'
import { all, call } from `redux-saga/effects`
function* mySaga() {
const [customers, products] = yield all([
call(fetchCustomers),
call(fetchProducts)
])
}
all(effects)
与 all([...effects])
相同,但允许您传入带有标签的 Effect 字典对象,就像 race(effects)
一样。
effects: Object
- 一个字典对象,形式为 {label: effect, ...}
示例
以下示例并行运行两个阻塞调用
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 时,中间件会挂起 Generator,直到以下情况之一发生
所有 Effect 成功完成:使用包含所有 Effect 结果的数组恢复 Generator。
在所有 Effect 完成之前,其中一个 Effect 被拒绝:在 Generator 中抛出拒绝错误。
接口
任务
Task 接口指定使用 fork
、middleware.run
或 runSaga
运行 Saga 的结果。
方法 | 返回值 |
---|---|
task.isRunning() | 如果任务尚未返回或抛出错误,则为 true |
task.isCancelled() | 如果任务已被取消,则为 true |
task.result() | 任务返回值。如果任务仍在运行,则为 `undefined` |
task.error() | 任务抛出的错误。如果任务仍在运行,则为 `undefined` |
task.toPromise() | 一个 Promise,它要么
|
task.cancel() | 取消任务(如果它仍在运行) |
通道
通道是一个用于在任务之间发送和接收消息的对象。来自发送者的消息将被排队,直到感兴趣的接收者请求消息,并且已注册的接收者将被排队,直到有消息可用。
每个通道都有一个底层缓冲区,它定义了缓冲策略(固定大小、丢弃、滑动)
Channel 接口定义了 3 个方法:take
、put
和 close
Channel.take(callback):
用于注册一个接收者。接收操作将使用以下规则解析
- 如果通道有缓冲消息,则
callback
将使用底层缓冲区中的下一条消息调用(使用buffer.take()
) - 如果通道已关闭且没有缓冲消息,则
callback
将使用END
调用 - 否则,
callback
将被排队,直到消息被放入通道
Channel.put(message):
用于将消息放入缓冲区。放入操作将使用以下规则处理
- 如果通道已关闭,则放入操作将无效。
- 如果有待处理的接收者,则使用该消息调用最旧的接收者。
- 否则将消息放入底层缓冲区
Channel.flush(callback):
用于从通道中提取所有缓冲消息。刷新操作将使用以下规则解析
- 如果通道已关闭且没有缓冲消息,则
callback
将使用END
调用 - 否则,
callback
将使用所有缓冲消息调用。
Channel.close():
关闭通道,这意味着不再允许放入操作。所有待处理的接收者都将使用 END
调用。
缓冲区
用于实现通道的缓冲策略。Buffer 接口定义了 3 个方法:isEmpty
、put
和 take
isEmpty()
: 如果缓冲区中没有消息,则返回 true。通道在每次注册新的接收者时都会调用此方法put(message)
: 用于将新消息放入缓冲区。请注意,Buffer 可以选择不存储消息(例如,丢弃缓冲区可以丢弃超过给定限制的任何新消息)take()
用于检索任何缓冲的消息。请注意,此方法的行为必须与isEmpty
保持一致。
SagaMonitor
由中间件用于分发监控事件。实际上,中间件会分发 6 个事件。
当一个根 Saga 启动(通过
runSaga
或sagaMiddleware.run
)时,中间件会调用sagaMonitor.rootSagaStarted
。当一个 Effect 被触发(通过
yield someEffect
)时,中间件会调用sagaMonitor.effectTriggered
。如果 Effect 成功解析,中间件会调用
sagaMonitor.effectResolved
。如果 Effect 被错误拒绝,中间件会调用
sagaMonitor.effectRejected
。如果 Effect 被取消,中间件会调用
sagaMonitor.effectCancelled
。最后,中间件会在分发 Redux Action 时调用
sagaMonitor.actionDispatched
。
以下是每个方法的签名。
sagaMonitor.rootSagaStarted(options)
:其中 options 是一个包含以下字段的对象。effectId
:数字 - 分配给此根 Saga 执行的唯一 ID。saga
:函数 - 开始运行的生成器函数。args
:数组 - 传递给生成器函数的参数。
effectTriggered(options)
effectId
:数字 - 分配给已生成的 Effect 的唯一 ID。parentEffectId
:数字 - 父 Effect 的 ID。在race
或parallel
Effect 的情况下,内部生成的每个 Effect 都会将直接的 race/parallel Effect 作为父级。在顶级 Effect 的情况下,父级将是包含的 Saga。label
:字符串 - 在race
/all
Effect 的情况下,所有子 Effect 将被分配为传递给race
/all
的对象的相应键的标签。effect
:对象 - 已生成的 Effect 本身。
effectResolved(effectId, result)
effectId
:数字 - 已生成的 Effect 的 ID。result
:任何 - Effect 成功解析的结果。在fork
或spawn
Effect 的情况下,结果将是一个Task
对象。
effectRejected(effectId, error)
effectId
:数字 - 已生成的 Effect 的 ID。error
:任何 - Effect 拒绝时引发的错误。
effectCancelled(effectId)
effectId
:数字 - 已生成的 Effect 的 ID。
actionDispatched(action)
action
: 对象 - 派发的 Redux 动作。如果动作是由 Saga 派发的,则动作将具有一个属性SAGA_ACTION
设置为 true(SAGA_ACTION
可以从@redux-saga/symbols
导入)。
外部 API
runSaga(options, saga, ...args)
允许在 Redux 中间件环境之外启动 Saga。如果您想将 Saga 连接到外部输入/输出(而不是存储操作),这很有用。
runSaga
返回一个 Task 对象。就像从 fork
效果返回的那样。
options: 对象
- 当前支持的选项是channel
- 查看channel
的文档(最好在这里使用stdChannel
)dispatch(output): 函数
- 用于实现put
效果。output: any
- Saga 提供给put
效果的参数(见下面的注释)。
getState(): 函数
- 用于实现select
和getState
效果sagaMonitor
: SagaMonitor - 查看createSagaMiddleware(options)
的文档onError: 函数
- 查看createSagaMiddleware(options)
的文档context
: {} - 查看createSagaMiddleware(options)
的文档effectMiddlewares
: 函数[] - 查看createSagaMiddleware(options)
的文档
saga: Function
- 一个生成器函数args: Array<any>
- 要提供给saga
的参数
注释
{channel, dispatch}
用于实现 take
和 put
效果。这定义了 Saga 的输入/输出接口。
channel
用于实现 take(PATTERN)
效果。每次在通道上放置东西时,它都会通知所有挂起的内部监听器。如果 Saga 被阻塞在一个 take
效果上,并且如果 take 模式与当前传入的输入匹配,则 Saga 将使用该输入恢复。
dispatch
用于实现 put
效果。每次 Saga 发出一个 yield put(output)
时,都会使用 output 调用 dispatch
。
有关如何使用此 API 的示例,请参见此处。
实用程序
channel([buffer])
一个工厂方法,可用于创建通道。您可以选择向其传递一个缓冲区来控制通道如何缓冲消息。
默认情况下,如果未提供缓冲区,则通道将最多将传入消息排队 10 次,直到注册感兴趣的接收者。默认缓冲将使用 FIFO 策略传递消息:新的接收者将收到缓冲区中最旧的消息。
eventChannel(subscribe, [buffer])
创建将使用subscribe
方法订阅事件源的通道。来自事件源的传入事件将排队到通道中,直到注册感兴趣的接收者。
subscribe: Function
用于订阅底层事件源。该函数必须返回一个取消订阅函数以终止订阅。buffer: Buffer
可选的 Buffer 对象,用于在此通道上缓冲消息。如果未提供,则不会在此通道上缓冲消息。
要通知通道事件源已终止,您可以使用END
通知提供的订阅者。
示例
在以下示例中,我们创建一个事件通道,该通道将订阅setInterval
const countdown = (secs) => {
return eventChannel(emitter => {
const iv = setInterval(() => {
console.log('countdown', secs)
secs -= 1
if (secs > 0) {
emitter(secs)
} else {
emitter(END)
clearInterval(iv)
console.log('countdown terminated')
}
}, 1000);
return () => {
clearInterval(iv)
console.log('countdown cancelled')
}
}
)
}
buffers
提供一些常见的缓冲区
buffers.none()
:无缓冲,如果没有任何待处理的接收者,新消息将丢失buffers.fixed(limit)
:新消息将被缓冲到limit
。溢出将引发错误。省略limit
值将导致限制为 10。buffers.expanding(initialSize)
:类似于fixed
,但溢出将导致缓冲区动态扩展。buffers.dropping(limit)
:与fixed
相同,但溢出将静默丢弃消息。buffers.sliding(limit)
:与fixed
相同,但溢出将在末尾插入新消息并丢弃缓冲区中最旧的消息。
cloneableGenerator(generatorFunc)
接受一个生成器函数 (function*) 并返回一个生成器函数。从该函数实例化的所有生成器都将是可克隆的。仅用于测试目的。
示例
当您想要测试 saga 中的不同分支,而无需重放导致该分支的操作时,这很有用。
import { cloneableGenerator } from '@redux-saga/testing-utils';
function* oddOrEven() {
// some stuff are done here
yield 1;
yield 2;
yield 3;
const userInput = yield 'enter a number';
if (userInput % 2 === 0) {
yield 'even';
} else {
yield 'odd'
}
}
test('my oddOrEven saga', assert => {
const data = {};
data.gen = cloneableGenerator(oddOrEven)();
assert.equal(
data.gen.next().value,
1,
'it should yield 1'
);
assert.equal(
data.gen.next().value,
2,
'it should yield 2'
);
assert.equal(
data.gen.next().value,
3,
'it should yield 3'
);
assert.equal(
data.gen.next().value,
'enter a number',
'it should ask for a number'
);
assert.test('even number is given', a => {
// we make a clone of the generator before giving the number;
data.clone = data.gen.clone();
a.equal(
data.gen.next(2).value,
'even',
'it should yield "even"'
);
a.equal(
data.gen.next().done,
true,
'it should be done'
);
a.end();
});
assert.test('odd number is given', a => {
a.equal(
data.clone.next(1).value,
'odd',
'it should yield "odd"'
);
a.equal(
data.clone.next().done,
true,
'it should be done'
);
a.end();
});
assert.end();
});
createMockTask()
返回一个模拟任务的对象。仅用于测试目的。 有关更多信息,请参阅任务取消文档。 )
速查表
阻塞/非阻塞
名称 | 阻塞 |
---|---|
takeEvery | 否 |
takeLatest | 否 |
takeLeading | 否 |
throttle | 否 |
debounce | 否 |
retry | 是 |
take | 是 |
take(channel) | 有时(请参阅 API 参考) |
takeMaybe | 是 |
put | 否 |
putResolve | 是 |
put(channel, action) | 否 |
call | 是 |
apply | 是 |
cps | 是 |
fork | 否 |
spawn | 否 |
join | 是 |
cancel | 否 |
select | 否 |
actionChannel | 否 |
flush | 是 |
cancelled | 是 |
race | 是 |
delay | 是 |
all | 如果数组或对象中存在阻塞效果,则会阻塞 |