How to dispatch a Redux action with a timeout?
我有一个操作可以更新我的应用程序的通知状态。通常,此通知是一个错误或某种类型的信息。然后,我需要在5秒后调度另一个操作,将通知状态返回到初始状态,因此没有通知。这背后的主要原因是提供了在5秒后通知自动消失的功能。
我使用
不要陷入认为图书馆应该规定如何做每件事的陷阱。如果您想在javascript中处理超时问题,则需要使用
Redux确实提供了一些处理异步事务的替代方法,但是您应该只在意识到您重复了太多代码时使用这些方法。除非您有这个问题,否则请使用语言提供的内容,并寻求最简单的解决方案。好的。以内联方式写入异步代码
这是迄今为止最简单的方法。这里没有具体的简化方法。好的。
1 2 3 4 | store.dispatch({ type: 'SHOW_NOTIFICATION', text: 'You logged in.' }) setTimeout(() => { store.dispatch({ type: 'HIDE_NOTIFICATION' }) }, 5000) |
同样,从连接的组件内部:好的。
1 2 3 4 | this.props.dispatch({ type: 'SHOW_NOTIFICATION', text: 'You logged in.' }) setTimeout(() => { this.props.dispatch({ type: 'HIDE_NOTIFICATION' }) }, 5000) |
唯一的区别是,在一个连接的组件中,您通常无法访问存储本身,而是将
如果您不喜欢在从不同的组件调度相同的操作时输入错误,那么您可能希望提取操作创建者而不是内联调度操作对象:好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | // actions.js export function showNotification(text) { return { type: 'SHOW_NOTIFICATION', text } } export function hideNotification() { return { type: 'HIDE_NOTIFICATION' } } // component.js import { showNotification, hideNotification } from '../actions' this.props.dispatch(showNotification('You just logged in.')) setTimeout(() => { this.props.dispatch(hideNotification()) }, 5000) |
或者,如果您以前用
1 2 3 4 | this.props.showNotification('You just logged in.') setTimeout(() => { this.props.hideNotification() }, 5000) |
到目前为止,我们还没有使用任何中间件或其他高级概念。好的。正在提取异步操作创建者
上面的方法在简单的情况下可以很好地工作,但您可能会发现它有一些问题:好的。
- 它强制您在任何想要显示通知的地方复制这个逻辑。
- 通知没有ID,因此如果您足够快地显示两个通知,您将具有竞争条件。当第一个超时结束时,它将发送
HIDE_NOTIFICATION ,错误地在超时之后提前隐藏第二个通知。
要解决这些问题,您需要提取一个集中超时逻辑并分派这两个操作的函数。可能是这样的:好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | // actions.js function showNotification(id, text) { return { type: 'SHOW_NOTIFICATION', id, text } } function hideNotification(id) { return { type: 'HIDE_NOTIFICATION', id } } let nextNotificationId = 0 export function showNotificationWithTimeout(dispatch, text) { // Assigning IDs to notifications lets reducer ignore HIDE_NOTIFICATION // for the notification that is not currently visible. // Alternatively, we could store the timeout ID and call // clearTimeout(), but we’d still want to do it in a single place. const id = nextNotificationId++ dispatch(showNotification(id, text)) setTimeout(() => { dispatch(hideNotification(id)) }, 5000) } |
现在,组件可以使用
1 2 3 4 5 | // component.js showNotificationWithTimeout(this.props.dispatch, 'You just logged in.') // otherComponent.js showNotificationWithTimeout(this.props.dispatch, 'You just logged out.') |
为什么
如果您有一个从某个模块中导出的单件商店,您可以直接导入它和
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | // store.js export default createStore(reducer) // actions.js import store from './store' // ... let nextNotificationId = 0 export function showNotificationWithTimeout(text) { const id = nextNotificationId++ store.dispatch(showNotification(id, text)) setTimeout(() => { store.dispatch(hideNotification(id)) }, 5000) } // component.js showNotificationWithTimeout('You just logged in.') // otherComponent.js showNotificationWithTimeout('You just logged out.') |
这看起来更简单,但我们不推荐这种方法。我们不喜欢它的主要原因是它迫使商店成为一个单身汉。这使得实现服务器呈现非常困难。在服务器上,您希望每个请求都有自己的存储区,以便不同的用户获得不同的预加载数据。好的。
单件商店也使测试更加困难。当测试动作创建者时,您不能再模拟存储,因为它们引用了从特定模块导出的特定实存储。你甚至不能从外部重置它的状态。好的。
因此,虽然从技术上讲,您可以从模块中导出单例存储,但我们不鼓励这样做。不要这样做,除非你确定你的应用永远不会添加服务器渲染。好的。
返回到以前的版本:好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | // actions.js // ... let nextNotificationId = 0 export function showNotificationWithTimeout(dispatch, text) { const id = nextNotificationId++ dispatch(showNotification(id, text)) setTimeout(() => { dispatch(hideNotification(id)) }, 5000) } // component.js showNotificationWithTimeout(this.props.dispatch, 'You just logged in.') // otherComponent.js showNotificationWithTimeout(this.props.dispatch, 'You just logged out.') |
这就解决了逻辑重复的问题,并将我们从竞争环境中拯救出来。好的。thunk中间件
对于简单的应用程序,这种方法应该足够了。如果您满意的话,就不要担心中间件。好的。
但是,在大型应用程序中,您可能会发现周围存在一些不便。好的。
例如,我们不得不绕过
此外,还很难记住哪些函数是同步动作创建者(如
这就是找到一种"使"这种向辅助函数提供
如果您仍然和我们在一起,并且您也认识到应用程序中存在问题,欢迎您使用ReduxThunk中间件。好的。
在一个要点中,redux thunk教redux认识到实际上是函数的特殊类型的动作:好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | import { createStore, applyMiddleware } from 'redux' import thunk from 'redux-thunk' const store = createStore( reducer, applyMiddleware(thunk) ) // It still recognizes plain object actions store.dispatch({ type: 'INCREMENT' }) // But with thunk middleware, it also recognizes functions store.dispatch(function (dispatch) { // ... which themselves may dispatch many times dispatch({ type: 'INCREMENT' }) dispatch({ type: 'INCREMENT' }) dispatch({ type: 'INCREMENT' }) setTimeout(() => { // ... even asynchronously! dispatch({ type: 'DECREMENT' }) }, 1000) }) |
当启用此中间件时,如果您分派一个函数,redux thunk中间件将把它作为参数
这个看起来不太有用,是吗?不是在这种特殊情况下。但是,它让我们声明
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | // actions.js function showNotification(id, text) { return { type: 'SHOW_NOTIFICATION', id, text } } function hideNotification(id) { return { type: 'HIDE_NOTIFICATION', id } } let nextNotificationId = 0 export function showNotificationWithTimeout(text) { return function (dispatch) { const id = nextNotificationId++ dispatch(showNotification(id, text)) setTimeout(() => { dispatch(hideNotification(id)) }, 5000) } } |
请注意,函数与我们在前一节中编写的函数几乎相同。但它不接受
我们如何在组件中使用它?当然,我们可以这样写:好的。
1 2 | // component.js showNotificationWithTimeout('You just logged in.')(this.props.dispatch) |
我们调用异步操作创建者来获取只需要
不过,这比原来的版本更尴尬!我们为什么要走那条路?好的。
因为我以前告诉过你的。如果启用了redux thunk中间件,任何时候您试图调度一个函数而不是一个操作对象,中间件都会以
所以我们可以这样做:好的。
1 2 | // component.js this.props.dispatch(showNotificationWithTimeout('You just logged in.')) |
最后,分派一个异步动作(实际上是一系列动作)与同步分派一个动作到组件没有什么不同。这很好,因为组件不应该关心某些事情是同步发生还是异步发生。我们只是把它抽象化了。好的。
注意,由于我们"教"了Redux来识别这种"特殊"的动作创建者(我们称之为thunk动作创建者),我们现在可以在任何我们使用常规动作创建者的地方使用它们。例如,我们可以将它们与
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | // actions.js function showNotification(id, text) { return { type: 'SHOW_NOTIFICATION', id, text } } function hideNotification(id) { return { type: 'HIDE_NOTIFICATION', id } } let nextNotificationId = 0 export function showNotificationWithTimeout(text) { return function (dispatch) { const id = nextNotificationId++ dispatch(showNotification(id, text)) setTimeout(() => { dispatch(hideNotification(id)) }, 5000) } } // component.js import { connect } from 'react-redux' // ... this.props.showNotificationWithTimeout('You just logged in.') // ... export default connect( mapStateToProps, { showNotificationWithTimeout } )(MyComponent) |
雷鸣状态
通常,减速器包含用于确定下一个状态的业务逻辑。但是,减速器只有在动作发出后才会启动。如果您在thunk操作创建者中有副作用(例如调用API),并且您希望在某些情况下阻止它发生,那该怎么办?好的。
如果不使用thunk中间件,只需在组件内部进行检查:好的。
1 2 3 4 | // component.js if (this.props.areNotificationsEnabled) { showNotificationWithTimeout(this.props.dispatch, 'You just logged in.') } |
然而,提取动作创建者的目的是将这个重复的逻辑集中在许多组件上。幸运的是,ReduxThunk为您提供了一种读取ReduxStore当前状态的方法。除了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | let nextNotificationId = 0 export function showNotificationWithTimeout(text) { return function (dispatch, getState) { // Unlike in a regular action creator, we can exit early in a thunk // Redux doesn’t care about its return value (or lack of it) if (!getState().areNotificationsEnabled) { return } const id = nextNotificationId++ dispatch(showNotification(id, text)) setTimeout(() => { dispatch(hideNotification(id)) }, 5000) } } |
不要滥用这种模式。当缓存数据可用时,有助于清除API调用,但这不是建立业务逻辑的很好基础。如果只使用
既然您对thunk是如何工作有了基本的直觉,那么请查看使用它们的redux异步示例。好的。
你可能会发现很多例子,其中thunks返回承诺。这不是必需的,但非常方便。Redux不关心从thunk返回什么,但它给出了从
您还可以将复杂的thunk动作创建者拆分为几个较小的thunk动作创建者。thunks提供的
对于某些应用程序,您可能会发现您的异步控制流需求过于复杂,无法用thunk表示。例如,当以这种方式写入时,重试失败的请求、使用令牌的重新授权流或逐步启动可能过于冗长且容易出错。在这种情况下,您可能希望了解更高级的异步控制流解决方案,如Redux SAGA或Redux循环。对它们进行评估,比较与您的需求相关的例子,然后选择您最喜欢的一个。好的。
最后,如果您没有真正的需求,不要使用任何东西(包括thunk)。请记住,根据需要,您的解决方案可能看起来像好的。
1 2 3 4 | store.dispatch({ type: 'SHOW_NOTIFICATION', text: 'You logged in.' }) setTimeout(() => { store.dispatch({ type: 'HIDE_NOTIFICATION' }) }, 5000) |
除非你知道你为什么要这样做,否则别担心。好的。好啊。
使用Redux Saga
正如DanAbramov所说,如果您想要对异步代码进行更高级的控制,您可以看看ReduxSaga。
这个答案是一个简单的例子,如果您想要更好的解释为什么Redux SAGA对您的应用程序有用,请检查另一个答案。
一般的想法是,ReduxSaga提供了一个ES6生成器解释器,允许您轻松地编写看起来像同步代码的异步代码(这就是为什么在ReduxSaga中经常会发现无限循环)。不知何故,ReduxSaga正在直接在JavaScript内部构建自己的语言。ReduxSaga一开始可能会觉得有点难以学习,因为您需要对生成器有基本的了解,而且还需要了解ReduxSaga提供的语言。
我将尝试在这里描述我在Redux Saga之上构建的通知系统。此示例当前在生产中运行。
高级通知系统规范- 您可以请求显示通知
- 您可以请求通知隐藏
- 通知的显示时间不应超过4秒
- 可以同时显示多个通知
- 同时最多可显示3个通知
- 如果在已显示3个通知的情况下请求通知,则将其排队/推迟。
结果
我的生产应用程序stample.co的屏幕截图
代码这里我将通知命名为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | function* toastSaga() { // Some config constants const MaxToasts = 3; const ToastDisplayTime = 4000; // Local generator state: you can put this state in Redux store // if it's really important to you, in my case it's not really let pendingToasts = []; // A queue of toasts waiting to be displayed let activeToasts = []; // Toasts currently displayed // Trigger the display of a toast for 4 seconds function* displayToast(toast) { if ( activeToasts.length >= MaxToasts ) { throw new Error("can't display more than" + MaxToasts +" at the same time"); } activeToasts = [...activeToasts,toast]; // Add to active toasts yield put(events.toastDisplayed(toast)); // Display the toast (put means dispatch) yield call(delay,ToastDisplayTime); // Wait 4 seconds yield put(events.toastHidden(toast)); // Hide the toast activeToasts = _.without(activeToasts,toast); // Remove from active toasts } // Everytime we receive a toast display request, we put that request in the queue function* toastRequestsWatcher() { while ( true ) { // Take means the saga will block until TOAST_DISPLAY_REQUESTED action is dispatched const event = yield take(Names.TOAST_DISPLAY_REQUESTED); const newToast = event.data.toastData; pendingToasts = [...pendingToasts,newToast]; } } // We try to read the queued toasts periodically and display a toast if it's a good time to do so... function* toastScheduler() { while ( true ) { const canDisplayToast = activeToasts.length < MaxToasts && pendingToasts.length > 0; if ( canDisplayToast ) { // We display the first pending toast of the queue const [firstToast,...remainingToasts] = pendingToasts; pendingToasts = remainingToasts; // Fork means we are creating a subprocess that will handle the display of a single toast yield fork(displayToast,firstToast); // Add little delay so that 2 concurrent toast requests aren't display at the same time yield call(delay,300); } else { yield call(delay,50); } } } // This toast saga is a composition of 2 smaller"sub-sagas" (we could also have used fork/spawn effects here, the difference is quite subtile: it depends if you want toastSaga to block) yield [ call(toastRequestsWatcher), call(toastScheduler) ] } |
减速器:
1 2 3 4 5 6 7 8 9 10 | const reducer = (state = [],event) => { switch (event.name) { case Names.TOAST_DISPLAYED: return [...state,event.data.toastData]; case Names.TOAST_HIDDEN: return _.without(state,event.data.toastData); default: return state; } }; |
用法
您可以简单地发送
注意,我并不特别推荐从JSX发送
我的代码并不完美,但在生产中运行了几个月,其中有0个bug。ReduxSaga和Generator最初有点困难,但一旦你理解了它们,这种系统就很容易构建。
更复杂的规则更容易实现,比如:
- 当太多的通知被"排队"时,为每个通知提供较少的显示时间,以便队列大小可以更快地减小。
- 检测窗口大小的变化,并相应地更改显示的最大通知数(例如,桌面=3,电话纵向=2,电话横向=1)
诚然,祝你好运,用thunks正确地实现这类工作。
注意,对于Redux Observable,您可以做完全相同的事情,这与Redux Saga非常相似。这几乎是相同的,是一个问题的味道之间的发电机和RXJ。
我还建议您看看SAM模式。
SAM模式主张包括"下一步动作谓词",其中(自动)动作,如"通知在5秒后自动消失"在模型更新后触发(SAM模型~ Reducer状态+存储)。
模式提倡一次一个动作排序和模型突变,因为模型"控制"的"控制状态"控制下一个动作谓词启用和/或自动执行的动作。您无法预测(一般而言)系统在处理操作之前的状态,因此您的下一个预期操作是否被允许/可能。
例如代码,
1 2 3 4 5 6 7 8 | export function showNotificationWithTimeout(dispatch, text) { const id = nextNotificationId++ dispatch(showNotification(id, text)) setTimeout(() => { dispatch(hideNotification(id)) }, 5000) } |
不允许与sam一起使用,因为可以调度hidenotification操作的事实取决于成功接受值"shownotification:true"的模型。模型的其他部分可能会阻止它接受它,因此,没有理由触发hidenotification操作。
我强烈建议在存储更新之后实现适当的下一步动作谓词,并且可以知道模型的新控制状态。这是实现您正在寻找的行为的最安全的方法。
如果你愿意的话,你可以在吉特加入我们。这里还有一个山姆入门指南。
你可以用redux thunk来实现。Redux文档中有一个关于异步操作(如setTimeout)的指南。
包含示例项目的存储库
目前有四个示例项目:
公认的答案太棒了。
但有些东西不见了:
因此,我创建了hello async存储库来添加缺少的内容:
- 复仇传奇
- 回流环
- …
复仇传奇
接受的答案已经为异步代码内联、异步操作生成器和redux thunk提供了示例代码段。为了完整起见,我提供了redux saga的代码片段:
1 2 3 4 5 6 7 8 9 10 11 12 13 | // actions.js export const showNotification = (id, text) => { return { type: 'SHOW_NOTIFICATION', id, text } } export const hideNotification = (id) => { return { type: 'HIDE_NOTIFICATION', id } } export const showNotificationWithTimeout = (text) => { return { type: 'SHOW_NOTIFICATION_WITH_TIMEOUT', text } } |
行动是简单而纯粹的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // component.js import { connect } from 'react-redux' // ... this.props.showNotificationWithTimeout('You just logged in.') // ... export default connect( mapStateToProps, { showNotificationWithTimeout } )(MyComponent) |
组件没有特殊之处。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | // sagas.js import { takeEvery, delay } from 'redux-saga' import { put } from 'redux-saga/effects' import { showNotification, hideNotification } from './actions' // Worker saga let nextNotificationId = 0 function* showNotificationWithTimeout (action) { const id = nextNotificationId++ yield put(showNotification(id, action.text)) yield delay(5000) yield put(hideNotification(id)) } // Watcher saga, will invoke worker saga above upon action 'SHOW_NOTIFICATION_WITH_TIMEOUT' function* notificationSaga () { yield takeEvery('SHOW_NOTIFICATION_WITH_TIMEOUT', showNotificationWithTimeout) } export default notificationSaga |
Sagas基于ES6发电机
1 2 3 4 5 6 7 8 9 10 11 12 13 | // index.js import createSagaMiddleware from 'redux-saga' import saga from './sagas' const sagaMiddleware = createSagaMiddleware() const store = createStore( reducer, applyMiddleware(sagaMiddleware) ) sagaMiddleware.run(saga) |
与Redux Thunk相比赞成的意见
- 你不会死在地狱里。
- 您可以轻松地测试异步流。
- 你的行为保持纯洁。
欺骗
- 这取决于相对较新的ES6发电机。
如果上面的代码片段不能回答所有问题,请参考可运行项目。
在尝试了各种流行的方法(动作创建者、thunk、sagas、epics、效果、定制中间件)之后,我仍然觉得可能还有改进的空间,所以我在这篇博客文章中记录了我的旅程,我应该把我的业务逻辑放在react/redux应用程序的哪里?
就像这里的讨论一样,我试图对比和比较各种方法。最终,它引导我引入了一个新的库redux逻辑,它从epics、sagas和定制中间件中获得灵感。
它允许您截取操作来验证、验证、授权以及提供执行异步IO的方法。
一些常见的功能可以简单地声明为去块、限制、取消,并且只使用来自最新请求的响应(takeLatest)。Redux逻辑包装了为您提供此功能的代码。
这使您可以自由地实现您喜欢的核心业务逻辑。除非你愿意,否则你不必使用观测仪或发电机。使用函数和回调、承诺、异步函数(异步/等待)等。
执行简单5S通知的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | const notificationHide = createLogic({ // the action type that will trigger this logic type: 'NOTIFICATION_DISPLAY', // your business logic can be applied in several // execution hooks: validate, transform, process // We are defining our code in the process hook below // so it runs after the action hit reducers, hide 5s later process({ getState, action }, dispatch) { setTimeout(() => { dispatch({ type: 'NOTIFICATION_CLEAR' }); }, 5000); } }); |
我的repo中有一个更高级的通知示例,其工作原理类似于Sebastian Lorber所描述的,您可以将显示限制为n个项目,并在排队的任何项目中旋转。Redux逻辑通知示例
我有各种各样的Redux Logic JSfiddle实时示例以及完整的示例。我将继续研究文档和示例。
我很想听听你的反馈。
我知道这个问题有点老,但我将介绍另一个使用Redux Observable aka的解决方案。史诗。
引用官方文件:
什么是Redux Observable?
RxJS 5-based middleware for Redux. Compose and cancel async actions to
create side effects and more.
史诗是还原可观测的核心原语。
It is a function which takes a stream of actions and returns a stream
of actions. Actions in, actions out.
或多或少,您可以创建一个函数,它通过流接收操作,然后返回一个新的操作流(使用常见的副作用,如超时、延迟、间隔和请求)。
让我把代码贴出来,然后再解释一下
JS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | import {createStore, applyMiddleware} from 'redux' import {createEpicMiddleware} from 'redux-observable' import {Observable} from 'rxjs' const NEW_NOTIFICATION = 'NEW_NOTIFICATION' const QUIT_NOTIFICATION = 'QUIT_NOTIFICATION' const NOTIFICATION_TIMEOUT = 2000 const initialState = '' const rootReducer = (state = initialState, action) => { const {type, message} = action console.log(type) switch(type) { case NEW_NOTIFICATION: return message break case QUIT_NOTIFICATION: return initialState break } return state } const rootEpic = (action$) => { const incoming = action$.ofType(NEW_NOTIFICATION) const outgoing = incoming.switchMap((action) => { return Observable.of(quitNotification()) .delay(NOTIFICATION_TIMEOUT) //.takeUntil(action$.ofType(NEW_NOTIFICATION)) }); return outgoing; } export function newNotification(message) { return ({type: NEW_NOTIFICATION, message}) } export function quitNotification(message) { return ({type: QUIT_NOTIFICATION, message}); } export const configureStore = () => createStore( rootReducer, applyMiddleware(createEpicMiddleware(rootEpic)) ) |
索引文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; import {configureStore} from './store.js' import {Provider} from 'react-redux' const store = configureStore() ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') ); |
App.JS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | import React, { Component } from 'react'; import {connect} from 'react-redux' import {newNotification} from './store.js' class App extends Component { render() { return ( {this.props.notificationExistance ? (<p> {this.props.notificationMessage} </p>) : ''} <button onClick={this.props.onNotificationRequest}>Click!</button> ); } } const mapStateToProps = (state) => { return { notificationExistance : state.length > 0, notificationMessage : state } } const mapDispatchToProps = (dispatch) => { return { onNotificationRequest: () => dispatch(newNotification(new Date().toDateString())) } } export default connect(mapStateToProps, mapDispatchToProps)(App) |
解决这个问题的关键代码就像饼图一样简单,唯一不同于其他答案的是函数rootepic。
要点1。与Sagas一样,您必须组合epics才能获得接收操作流并返回操作流的顶级函数,因此您可以将其与中间件工厂createpicmiddleware一起使用。在我们的情况下,我们只需要一个,所以我们只有我们的rootepic,所以我们不需要结合任何东西,但这是一个很好的了解事实。
要点2。我们关注副作用逻辑的rootepic只需要5行代码,这太棒了!包括这个事实,这是非常声明性的!
要点3。逐行rootepic解释(在注释中)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | const rootEpic = (action$) => { // sets the incoming constant as a stream // of actions with type NEW_NOTIFICATION const incoming = action$.ofType(NEW_NOTIFICATION) // Merges the"incoming" stream with the stream resulting for each call // This functionality is similar to flatMap (or Promise.all in some way) // It creates a new stream with the values of incoming and // the resulting values of the stream generated by the function passed // but it stops the merge when incoming gets a new value SO!, // in result: no quitNotification action is set in the resulting stream // in case there is a new alert const outgoing = incoming.switchMap((action) => { // creates of observable with the value passed // (a stream with only one node) return Observable.of(quitNotification()) // it waits before sending the nodes // from the Observable.of(...) statement .delay(NOTIFICATION_TIMEOUT) }); // we return the resulting stream return outgoing; } |
希望有帮助!
为什么这么难?这只是用户界面逻辑。使用专用操作设置通知数据:
1 | dispatch({ notificationData: { message: 'message', expire: +new Date() + 5*1000 } }) |
以及显示它的专用组件:
1 2 3 4 5 | const Notifications = ({ notificationData }) => { if(notificationData.expire > this.state.currentTime) { return {notificationData.message} } else return null; } |
在这种情况下,问题应该是"如何清理旧状态?","如何通知组件时间已更改"
您可以实现一些超时操作,这些操作在组件的setTimeout上调度。
也许每次显示新通知时都可以清除它。
不管怎么说,应该在某个地方有一些
1 2 | setTimeout(() => this.setState({ currentTime: +new Date()}), this.props.notificationData.expire-(+new Date()) ) |
其动机是"通知淡出"功能实际上是一个用户界面问题。因此,它简化了对业务逻辑的测试。
测试它是如何实现的似乎没有意义。只有验证通知何时应该超时才有意义。因此,代码到存根更少,测试更快,代码更干净。
如果您希望对选择性操作进行超时处理,可以尝试使用中间件方法。我在有选择地处理基于承诺的行为时遇到了类似的问题,这个解决方案更加灵活。
假设您的动作创建者如下所示:
1 2 3 4 5 | //action creator buildAction = (actionData) => ({ ...actionData, timeout: 500 }) |
超时可以在上述操作中保存多个值
- 以毫秒为单位的数字-特定超时持续时间
- 真-持续的超时时间。(在中间件中处理)
- 未定义-用于立即调度
您的中间件实现如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | //timeoutMiddleware.js const timeoutMiddleware = store => next => action => { //If your action doesn't have any timeout attribute, fallback to the default handler if(!action.timeout) { return next (action) } const defaultTimeoutDuration = 1000; const timeoutDuration = Number.isInteger(action.timeout) ? action.timeout || defaultTimeoutDuration; //timeout here is called based on the duration defined in the action. setTimeout(() => { next (action) }, timeoutDuration) } |
现在,您可以使用redux将所有操作路由到此中间件层。
1 | createStore(reducer, applyMiddleware(timeoutMiddleware)) |
你可以在这里找到一些类似的例子
正确的方法是使用redux thunk,它是Redux的流行中间件,根据Redux Thunk文档:
"Redux Thunk middleware allows you to write action creators that
return a function instead of an action. The thunk can be used to delay
the dispatch of an action, or to dispatch only if a certain condition
is met. The inner function receives the store methods dispatch and
getState as parameters".
所以基本上它返回一个函数,并且您可以延迟调度或者将其置于条件状态。
所以像这样的事情会为你做:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | import ReduxThunk from 'redux-thunk'; const INCREMENT_COUNTER = 'INCREMENT_COUNTER'; function increment() { return { type: INCREMENT_COUNTER }; } function incrementAsync() { return dispatch => { setTimeout(() => { // Yay! Can invoke sync or async actions with `dispatch` dispatch(increment()); }, 5000); }; } |
这很简单。使用trim redux包,在
1 2 3 4 5 6 7 8 9 | componentDidMount() { this.tm = setTimeout(function() { setStore({ age: 20 }); }, 3000); } componentWillUnmount() { clearTimeout(this.tm); } |
redux本身是一个非常冗长的库,对于这种东西,您必须使用redux thunk之类的东西,它将提供
我创建了一个库来解决诸如冗长性和可组合性等问题,您的示例如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | import { createTile, createSyncTile } from 'redux-tiles'; import { sleep } from 'delounce'; const notifications = createSyncTile({ type: ['ui', 'notifications'], fn: ({ params }) => params.data, // to have only one tile for all notifications nesting: ({ type }) => [type], }); const notificationsManager = createTile({ type: ['ui', 'notificationManager'], fn: ({ params, dispatch, actions }) => { dispatch(actions.ui.notifications({ type: params.type, data: params.data })); await sleep(params.timeout || 5000); dispatch(actions.ui.notifications({ type: params.type, data: null })); return { closed: true }; }, nesting: ({ type }) => [type], }); |
因此,我们编写同步操作来显示异步操作中的通知,这可以请求一些后台信息,或者稍后检查通知是否是手动关闭的。