我有一个更新应用程序通知状态的操作.通常,此通知将是某种错误或信息.然后,我需要在5秒后调度另一个操作,该操作将通知状态返回到初始状态,因此没有通知.这背后的主要原因是提供通知在5秒后自动消失的功能.
我没有运气使用setTimeout
并返回另一个动作,也找不到这是如何在网上完成的.所以任何建议都是欢迎的.
我有一个更新应用程序通知状态的操作.通常,此通知将是某种错误或信息.然后,我需要在5秒后调度另一个操作,该操作将通知状态返回到初始状态,因此没有通知.这背后的主要原因是提供通知在5秒后自动消失的功能.
我没有运气使用setTimeout
并返回另一个动作,也找不到这是如何在网上完成的.所以任何建议都是欢迎的.
不要落入trap of thinking a library should prescribe how to do everything人之列.如果你想在JavaScript中用超时来做一些事情,你需要使用setTimeout
.没有理由认为Redux操作应该有所不同.
Redux does提供了一些处理异步内容的替代方法,但只有当您意识到自己重复了太多代码时,才应该使用这些方法.除非你有这个问题,否则使用语言提供的,并寻求最简单的解决方案.
这是迄今为止最简单的方法.这里没有什么特定于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)
唯一的区别是,在一个连接的组件中,你通常无法访问store 本身,但可以将dispatch()
个或特定的动作创建者作为props 注入.然而,这对我们没有任何影响.
如果您不喜欢在从不同组件调度相同操作时出现打字错误,则可能需要提取操作创建者,而不是内联调度操作对象:
// 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)
到目前为止,我们还没有使用任何中间件或其他高级概念.
上述方法在简单的情况下效果很好,但您可能会发现它有一些问题:
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 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)
}
现在,组件可以使用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.')
这看起来很简单,但却是we don’t recommend this approach.我们不喜欢它的主要原因是.这使得实现server rendering非常困难.在服务器上,您将希望每个请求都有自己的存储,以便不同的用户获得不同的预加载数据.
单件store 也让测试变得更加困难.在测试action Creator时,您不能再模拟store ,因为它们引用从特定模块导出的特定真实store .你甚至不能从外部重置它的状态.
因此,虽然从技术上讲,您可以从模块导出单例存储,但我们不鼓励这样做.除非你确信你的应用程序永远不会添加服务器渲染,否则不要这样做.
返回到以前的版本:
// 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.')
这解决了逻辑重复的问题,并将我们从竞争条件中解救出来.
对于简单的应用程序,这种方法应该足够了.如果你对中间件满意,就不要担心它.
然而,在更大的应用程序中,你可能会发现一些不便之处.
例如,我们必须通过dispatch
考试,这似乎很不幸.这使得separate container and presentational components变得更加棘手,因为任何以上述方式异步调度Redux操作的组件都必须接受dispatch
作为一个props ,以便它可以进一步传递它.你不能再把connect()
和动作创造者绑定在一起了,因为showNotificationWithTimeout()
并不是真正的动作创造者.它不会返回Redux操作.
此外,记住哪些函数是像showNotification()
这样的同步动作创建者,哪些是像showNotificationWithTimeout()
这样的异步助手,可能会很尴尬.你必须以不同的方式使用它们,小心不要把它们弄错.
这是finding a way to “legitimize” this pattern of providing 100 to a helper function, and help Redux “see” such asynchronous action creators as a special case of normal action creators的动机,而不是完全不同的功能.
如果您还在使用我们,并且您也认识到您的应用程序中存在问题,那么欢迎您使用Redux Thunk中间件.
简而言之,Redux Thunk教Redux识别实际上是函数的特殊类型的动作:
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)
})
当这个中间件启用if you dispatch a function时,Redux Thunk中间件将给出dispatch
作为参数.它也会"吞下"这样的动作,所以不用担心你的还原程序会收到奇怪的函数参数.您的减缩器将只接收直接发出的普通对象操作,或我们刚才描述的函数发出的普通对象操作.
这看起来不是很有用,是吗?在这种特殊情况下不会.但是,它允许我们将showNotificationWithTimeout()
声明为常规Redux动作创建者:
// 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)
我们调用async action creator来获取只需要dispatch
的内部函数,然后我们通过dispatch
.
然而,这比原来的版本更尴尬!我们为什么要走那条路?
因为我之前告诉过你的.If Redux Thunk middleware is enabled, any time you attempt to dispatch a function instead of an action object, the middleware will call that function with 100 method itself as the first argument
所以我们可以这样做:
// component.js
this.props.dispatch(showNotificationWithTimeout('You just logged in.'))
最后,调度异步操作(实际上是一系列操作)看起来与向组件同步调度单个操作没有什么不同.这很好,因为组件不应该关心某些事情是同步发生的还是异步发生的.我们只是把它抽象出来了.
请注意,由于我们"教会"Redux认识这些"特殊"动作创作者(我们称他们为thunk个动作创作者),我们现在可以在任何地方使用他们,我们会使用常规动作创作者.例如,我们可以将其用于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 action creator中有副作用(例如调用API),并且在某些情况下想要阻止它,该怎么办?
在不使用thunk中间件的情况下,您只需在组件内执行以下判断:
// component.js
if (this.props.areNotificationsEnabled) {
showNotificationWithTimeout(this.props.dispatch, 'You just logged in.')
}
然而,提取动作创建者的目的是将这种重复的逻辑集中在许多组件上.幸运的是,Redux Thunk为您提供了一种了解Reduxstore 当前状态的方法.除了dispatch
,它还将getState
作为第二个参数传递给从thunk action创建者返回的函数.这使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)
}
}
不要滥用这种模式.当缓存数据可用时,有助于清除API调用,但这不是建立业务逻辑的很好基础.如果您仅使用getState()
来有条件地调度不同的操作,则考虑将业务逻辑放入还原器中.
既然你对thunks的工作原理有了基本的直觉,那就看看Redux async example吧.
你可能会发现很多例子,在这些例子中,thunks回报promise .这不是必需的,但非常方便.Redux不在乎你从thunk中返回什么,但它会从dispatch()
中给出它的返回值.这就是为什么你可以通过拨打dispatch(someThunkReturningPromise()).then(...)
来返回来自thunk的promise 并等待其完成.
您也可以将复杂的thunk动作创建者分成几个较小的thunk动作创建者.thunks提供的dispatch
方法本身可以接受thunks,因此您可以递归地应用该模式.同样,这与promise 配合使用效果最好,因为您可以在此基础上实现异步控制流.
对于某些应用程序,您可能会发现自己的异步控制流需求太复杂,无法用thunks来表达.例如,以这种方式编写时,重试失败的请求、使用令牌重新授权流或一步一步的登录可能过于冗长且容易出错.在本例中,您可能希望了解更高级的异步控制流解决方案,例如Redux Saga或Redux Loop.判断它们,比较与你的需求相关的例子,然后 Select 你最喜欢的一个.
最后,如果你没有真正需要的东西,不要使用任何东西(包括鼻塞).请记住,根据需求,您的解决方案可能看起来很简单,如下所示
store.dispatch({ type: 'SHOW_NOTIFICATION', text: 'You logged in.' })
setTimeout(() => {
store.dispatch({ type: 'HIDE_NOTIFICATION' })
}, 5000)
除非你知道自己为什么要这么做,否则别担心.