如何发送Redux动作超时?
我有一个更新我的应用程序的通知状态的操作。 通常,这个通知将是某种错误或信息。 我需要然后派遣另一个动作5秒后,将通知状态返回到最初的一个,所以没有通知。 这背后的主要原因是提供通知在5秒后自动消失的function。
我没有使用setTimeout
运气,并返回另一个行动,无法find这是如何做在线。 所以任何build议是值得欢迎的
不要陷入思考一个图书馆应该怎样处理所有事情的陷阱 。 如果你想在JavaScript中使用超时,你需要使用setTimeout
。 Redux操作没有任何不同之处。
Redux 确实提供了一些处理asynchronous事件的替代方法,但是只有当你意识到你重复了太多的代码时,才应该使用这些方法。 除非你有这个问题,否则使用这个语言提供的东西,去寻找最简单的解决scheme。
内联编写asynchronous代码
这是迄今为止最简单的方法。 这里没有什么特别的Redux。
store.dispatch({ type: 'SHOW_NOTIFICATION', text: 'You logged in.' }) setTimeout(() => { store.dispatch({ type: 'HIDE_NOTIFICATION' }) }, 5000)
同样,从连接组件内部:
this.props.dispatch({ type: 'SHOW_NOTIFICATION', text: 'You logged in.' }) setTimeout(() => { this.props.dispatch({ type: 'HIDE_NOTIFICATION' }) }, 5000)
唯一的区别是,在连接组件中,通常无法访问商店本身,但获取dispatch()
或作为道具注入的特定动作创build者。 但是这对我们没有任何影响。
如果在分派来自不同组件的相同操作时不喜欢进行拼写错误,则可能需要提取操作创build者,而不是分配内联操作对象:
// 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)
或者,如果您以前使用connect()
绑定了它们:
this.props.showNotification('You just logged in.') setTimeout(() => { this.props.hideNotification() }, 5000)
到目前为止,我们还没有使用任何中间件或其他先进的概念。
提取asynchronous动作创build器
上面的方法在简单情况下工作正常,但是您可能会发现它有几个问题:
- 它迫使你在想要显示通知的任何地方复制这个逻辑。
- 通知没有ID,所以如果您足够快地显示两个通知,您将会遇到竞争状况。 当第一个超时完成时,它将调度
HIDE_NOTIFICATION
,错误地隐藏第二个通知比在超时之后更早。
为了解决这些问题,您需要提取集中超时逻辑的函数,并调度这两个操作。 它可能看起来像这样:
// 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 interval ID and call // clearInterval(), but we'd still want to do it in a single place. const id = nextNotificationId++ dispatch(showNotification(id, text)) setTimeout(() => { dispatch(hideNotification(id)) }, 5000) }
现在,组件可以使用showNotificationWithTimeout
而不复制这个逻辑或具有不同通知的竞争条件:
// component.js showNotificationWithTimeout(this.props.dispatch, 'You just logged in.') // otherComponent.js showNotificationWithTimeout(this.props.dispatch, 'You just logged out.')
为什么showNotificationWithTimeout()
接受dispatch
作为第一个参数? 因为它需要将动作分派给商店。 通常情况下,一个组件可以访问dispatch
但是由于我们需要一个外部函数来控制调度,所以我们需要控制调度。
如果你有一个从某个模块导出的单件商店,你可以直接导入它,直接在它上面dispatch
:
// 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.')
这看起来更简单,但我们不build议这种方法 。 我们不喜欢它的主要原因是因为它迫使商店成为一个单身人士 。 这使得实现服务器渲染非常困难。 在服务器上,您会希望每个请求都有自己的存储,以便不同的用户可以获得不同的预加载数据。
单件商店也使testing更难。 testing动作创build者时,您不能再嘲笑商店,因为他们引用从特定模块导出的特定实际商店。 你甚至不能从外面重置它的状态。
所以,虽然技术上可以从模块中导出单件商店,但我们不鼓励它。 除非您确定您的应用永远不会添加服务器渲染,否则请勿这样做。
回到以前的版本:
// 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中间件
对于简单的应用程序,这种方法应该足够了。 如果您满意,不要担心中间件。
但是,在较大的应用程序中,您可能会发现一些不便之处。
比如说,我们不得不通过dispatch
来dispatch
。 这使得分离容器和表示组件变得更加棘手,因为以上述方式asynchronous分派Redux动作的组件必须接受dispatch
作为prop,以便进一步传递它。 因为showNotificationWithTimeout()
不是一个真正的动作创build器,所以你不能仅仅使用connect()
绑定动作创build器。 它不会返回一个Redux动作。
此外,记住哪些函数是同步动作创build者(如showNotification()
和哪些是asynchronous助手(如showNotificationWithTimeout()
会很尴尬。 你必须以不同的方式使用它们,小心不要把它们误认为是对方。
这是find一种方法来将这种提供dispatch
的方式“合法化”到辅助函数的动机,并帮助Redux“将”这样的asynchronous操作创build者视为正常操作创build者的特例,而不是完全不同的function。
如果您仍然和我们在一起,并且您也认识到您的应用程序存在问题,欢迎使用Redux Thunk中间件。
Redux Thunk教导Redux能够识别事实上function的特殊行为:
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中间件会把它作为一个参数dispatch
。 它也将“吞下”这样的行为,所以不要担心你的减速器接收奇怪的函数参数。 您的简化器只会接收简单的对象操作 – 或者直接发送,或者由我们刚才描述的函数发送。
这看起来不是很有用,是吗? 不是在这个特定的情况下。 不过,它让我们将showNotificationWithTimeout()
声明为常规的Redux动作创build者:
// 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) } }
注意这个函数和我们在前一节中写的函数几乎是一样的。 但是它不接受dispatch
作为第一个参数。 相反,它会返回一个接受dispatch
作为第一个参数的函数。
我们如何在组件中使用它? 当然,我们可以这样写:
// component.js showNotificationWithTimeout('You just logged in.')(this.props.dispatch)
我们正在调用asynchronous动作创build器来获得想要dispatch
的内部函数,然后我们通过dispatch
。
然而,这比原来的版本更尴尬! 为什么我们甚至这样呢?
因为我以前告诉过你 如果启用Redux Thunk中间件,则只要尝试分派函数而不是操作对象,中间件就会使用dispatch
方法本身作为第一个参数来调用该函数 。
所以我们可以这样做:
// component.js this.props.dispatch(showNotificationWithTimeout('You just logged in.'))
最后,调度一个asynchronous操作(实际上是一系列操作)看起来与将一个操作同步分配给组件是没有区别的。 这是很好的,因为组件不应该在乎是同步发生还是asynchronous发生。 我们把这个抽象掉了。
请注意,由于我们“教导”Redux识别这种“特殊”动作创build者(我们称之为thunk动作创build者),现在我们可以在任何使用常规动作创build者的地方使用它们。 例如,我们可以用connect()
来使用它们:
// 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的阅读状态
通常你的reducer包含确定下一个状态的业务逻辑。 然而,减速器只有在动作发出之后才会执行。 如果您在thunk动作创build者中有副作用(如调用API),并且想要在某种情况下阻止它呢?
如果不使用thunk中间件,只需在组件内执行以下操作:
// component.js if (this.props.areNotificationsEnabled) { showNotificationWithTimeout(this.props.dispatch, 'You just logged in.') }
然而,提取动作创build者的重点是将这种重复的逻辑集中在许多组件上。 幸运的是,Redux Thunk为您提供了一种读取 Redux商店当前状态的方法。 除了dispatch
,它还会将getState
作为第二个parameter passing给从thunk动作创build者返回的函数。 这让thunk读取商店的当前状态。
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) } }
不要滥用这种模式。 有caching的数据可用时,API调用是很好的,但它不是一个很好的基础来build立你的业务逻辑。 如果仅使用getState()
来有条件地分派不同的操作,请考虑将业务逻辑放入reducer中。
下一步
现在您已经对Thunk如何工作有了一个基本的认识,请查看使用它们的Redux asynchronous示例 。
你可能会发现很多例子,在这个例子中,thunk回复Promises。 这不是必需的,但可以非常方便。 Redux并不关心你从thunk返回的内容,但是它会从dispatch()
返回它的返回值。 这就是为什么你可以从一个thunk中返回一个Promise,然后通过调用dispatch(someThunkReturningPromise()).then(...)
等待它完成dispatch(someThunkReturningPromise()).then(...)
。
您也可以将复杂的thunk动作创作者分成几个较小的thunk动作创build者。 thunk提供的dispatch
方法本身可以接受thunk,所以可以recursion地应用这个模式。 同样,这对于Promise来说效果最好,因为您可以在其上实现asynchronous控制stream。
对于某些应用程序,您可能会发现自己处于asynchronous控制stream程要求过于复杂而无法用thunk表示的情况。 例如,重试失败的请求,使用令牌重新授权stream程,或者按照这种方式一步一步地上传可能太冗长而且容易出错。 在这种情况下,您可能需要查看更高级的asynchronous控制stream解决scheme,如Redux Saga或Redux Loop 。 评估他们,比较与你的需求相关的例子,并挑选你最喜欢的。
最后,如果你没有真正的需要,不要使用任何东西(包括thunk)。 请记住,根据要求,您的解决scheme可能看起来很简单
store.dispatch({ type: 'SHOW_NOTIFICATION', text: 'You logged in.' }) setTimeout(() => { store.dispatch({ type: 'HIDE_NOTIFICATION' }) }, 5000)
除非你知道你为什么这么做,否则不要为此而出汗。
使用Redux-saga
正如丹·阿布拉莫夫(Dan Abramov)所说,如果你想要对asynchronous代码进行更高级的控制,你可以看一下“最简单的事情”。
这个答案是一个简单的例子,如果你想更好地解释为什么在你的应用程序中可能会有用,请检查这个答案 。
一般的想法是Redux-saga提供了一个ES6生成器解释器,允许你轻松地编写看起来像同步代码的asynchronous代码(这就是为什么你经常会在Redux-saga中发现无限循环的原因)。 不知何故,Redux-saga直接在Javascript中构build自己的语言。 Redux-saga起初可能感觉有点困难,因为你需要对发生器有基本的了解,但是也要了解Redux-saga提供的语言。
我会在这里试着描述一下我build立在redux-saga之上的通知系统。 这个例子目前在生产中运行。
先进的通知系统规范
- 您可以请求显示通知
- 您可以请求隐藏通知
- 通知不应该显示超过4秒
- 多个通知可以同时显示
- 不超过3个通知可以同时显示
- 如果在已经显示3个通知的情况下请求通知,则排队/推迟通知。
结果
我的生产应用程序Stample.co的屏幕截图
码
来自生产应用Stample.co的代码。
在这里,我将通知命名为toast
但这是一个命名细节。
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) ] }
而减速机:
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; } };
用法
您可以简单地调度TOAST_DISPLAY_REQUESTED
事件。 如果您发送4个请求,则只会显示3个通知,并且第一个通知消失后,第四个通知会稍后显示。
请注意,我并不特别推荐从JSX调度TOAST_DISPLAY_REQUESTED
。 您宁愿添加另一个侦听已经存在的应用程序事件的传奇,然后派发TOAST_DISPLAY_REQUESTED
:触发通知的组件,不必与通知系统紧密耦合。
结论
我的代码并不完美,但在几个月的时间里运行了0个bug。 Redux-saga和生成器最初有点难,但是一旦你理解了它们,这种系统很容易构build。
实现更复杂的规则甚至是很容易的,比如:
- 当太多的通知“排队”时,每个通知的显示时间就会减less,这样队列的大小就会减less得更快。
- 检测窗口大小更改,并相应地更改显示的最大通知数(例如,desktop = 3,phone portrait = 2,phone landscape = 1)
很高兴,祝你好运,用thunk来正确地实施这种东西。
请注意,您可以使用与redux-saga非常相似的redux-observable来执行完全相同的操作。 这几乎是一样的,是发电机和RxJS之间的味道问题。
你可以用redux-thunk来做到这一点。 在redux文档中有一个像setTimeout那样的asynchronous操作指南 。
我会build议也看看SAM模式 。
一旦模型已经被更新(SAM模型〜减速器状态+存储),SAM模式主张包括“下一个动作谓词”,其中(自动)动作例如“通知在5秒后自动消失”被触发。
该模式提倡对动作和模型突变进行sorting,因为模型的“控制状态”“控制”哪些动作是由下一动作谓词启用和/或自动执行的。 您根本无法预测(一般情况下)系统在处理某个操作之前的状态,因此您的下一个预期操作是否会被允许/可能。
所以例如代码,
export function showNotificationWithTimeout(dispatch, text) { const id = nextNotificationId++ dispatch(showNotification(id, text)) setTimeout(() => { dispatch(hideNotification(id)) }, 5000) }
将不被允许使用SAM,因为可以调度hideNotification动作的事实取决于成功接受值“showNotication:true”的模型。 模型的其他部分可能会阻止它接受它,因此没有理由触发hideNotification动作。
我强烈build议在存储更新和模型的新控制状态可以知道之后实施适当的下一步动作谓词。 这是实现您正在查找的行为的最安全的方式。
如果您愿意,可以join我们的Gitter。 这里还有一个SAM入门指南 。
在尝试了各种stream行的方法(行动创造者,thunk,传奇,史诗,效果,定制中间件)后,我仍然觉得可能还有改进的空间,所以我在这篇博客文章中logging了我的旅程。一个React / Redux应用程序?
就像这里的讨论一样,我试图对比各种方法。 最终,它引导我引入了一个新的图书馆重复逻辑 ,从史诗,传奇,定制中间件中获取灵感。
它允许您截取动作来validation,validation,授权,以及提供执行asynchronousIO的方法。
一些常见的function可以简单地声明为反弹,限制,取消,并且只使用来自最新请求(takeLatest)的响应。 redux逻辑包装你的代码为你提供这个function。
这样可以让你自由地实现你的核心业务逻辑。 除非你想要,否则你不必使用可观测量或者生成器。 使用函数和callback,承诺,asynchronous函数(asynchronous/等待)等。
做一个简单的5s通知的代码将是这样的:
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); } });
具有示例项目的存储库
目前有四个样本项目:
- 内联编写asynchronous代码
- 提取asynchronous动作创build器
- 使用Redux Thunk
- 使用Redux佐贺
接受的答案是真棒。
但有一些缺失:
- 没有可运行的示例项目,只是一些代码片段。
- 没有其他替代scheme的示例代码,例如:
- Redux佐贺
所以我创build了Helloasynchronous库来添加缺less的东西:
- 可运行的项目。 您可以下载并运行它们,而无需修改。
- 提供更多select的示例代码:
- Redux佐贺
- Redux Loop
- …
Redux佐贺
已接受的答案已经提供了asynchronous代码内联,asynchronous操作生成器和Redux Thunk的示例代码片断。 为了完整起见,我提供了Redux Saga的代码片段:
// 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 } }
行动简单而纯粹。
// component.js import { connect } from 'react-redux' // ... this.props.showNotificationWithTimeout('You just logged in.') // ... export default connect( mapStateToProps, { showNotificationWithTimeout } )(MyComponent)
没有什么特别的组件。
// 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发生器
// 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相比
优点
- 你不会在callback地狱。
- 您可以轻松testing您的asynchronousstream程。
- 你的行为保持纯洁。
缺点
- 这取决于相对较新的ES6发电机。
如果上面的代码片段没有回答你所有的问题,请参考runnable项目 。
如果您希望select性操作的超时处理,您可以尝试中间件方法。 我面临类似的问题,有select地处理基于承诺的行为,这个解决scheme更加灵活。
让我们说你的动作创造者是这样的:
//action creator buildAction = (actionData) => ({ ...actionData, timeout: 500 })
timeout can hold multiple values in the above action
- number in ms – for a specific timeout duration
- true – for a constant timeout duration. (handled in the middleware)
- undefined – for immediate dispatch
Your middleware implementation would look like this:
//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) }
You can now route all your actions through this middleware layer using redux.
createStore(reducer, applyMiddleware(timeoutMiddleware))
You can find some similar examples here
I understand that this question is a bit old but I'm going to introduce another solution using redux-observable aka. Epic.
Quoting the official documentation:
What is redux-observable?
RxJS 5-based middleware for Redux. Compose and cancel async actions to create side effects and more.
An Epic is the core primitive of redux-observable.
It is a function which takes a stream of actions and returns a stream of actions. Actions in, actions out.
In more or less words, you can create a function that receives actions through a Stream and then return a new stream of actions (using common side effects such as timeouts, delays, intervals, and requests).
Let me post the code and then explain a bit more about it
store.js
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)) )
index.js
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
import React, { Component } from 'react'; import {connect} from 'react-redux' import {newNotification} from './store.js' class App extends Component { render() { return ( <div className="App"> {this.props.notificationExistance ? (<p>{this.props.notificationMessage}</p>) : ''} <button onClick={this.props.onNotificationRequest}>Click!</button> </div> ); } } 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)
The key code to solve this problem is as easy as pie as you can see, the only thing that appears different from the other answers is the function rootEpic.
Point 1. As with sagas, you have to combine the epics in order to get a top level function that receives a stream of actions and returns a stream of actions, so you can use it with the middleware factory createEpicMiddleware . In our case we only need one so we only have our rootEpic so we don't have to combine anything but it's a good to know fact.
Point 2. Our rootEpic which takes care about the side effects logic only takes about 5 lines of code which is awesome! Including the fact that is pretty much declarative!
Point 3. Line by line rootEpic explanation (in comments)
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; }
我希望它有帮助!
The appropriate way to do this is using Redux Thunk which is middleware for Redux, as per redux-thunk documentation:
"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".
So basically it returns a function, and you can delay your dispatch or put it in a condition state.
So something like this is going to do the job for you:
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); }; }
Redux itself is a pretty verbose library, and for such stuff you would have to use something like Redux-thunk , which will give a dispatch
function, so you will be able to dispatch closing of the notification after several seconds.
I have created a library to address issues like verbosity and composability, and your example will look like the following:
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], });
So we compose sync actions for showing notifications inside async action, which can request some info the background, or check later whether the notification was closed manually.