我有一个更新应用程序通知状态的操作.通常,此通知将是某种错误或信息.然后,我需要在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)

到目前为止,我们还没有使用任何中间件或其他高级概念.

提取异步操作创建者

上述方法在简单的情况下效果很好,但您可能会发现它有一些问题:

  • 它迫使你在任何你想显示通知的地方复制这个逻辑.
  • 通知没有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 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.')    

这解决了逻辑重复的问题,并将我们从竞争条件中解救出来.

Thunk中间件

对于简单的应用程序,这种方法应该足够了.如果你对中间件满意,就不要担心它.

然而,在更大的应用程序中,你可能会发现一些不便之处.

例如,我们必须通过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)

Thunks中的阅读状态

通常情况下,还原程序包含确定下一个状态的业务逻辑.然而,减速机只有在动作被调度后才会启动.如果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 SagaRedux Loop.判断它们,比较与你的需求相关的例子,然后 Select 你最喜欢的一个.

最后,如果你没有真正需要的东西,不要使用任何东西(包括鼻塞).请记住,根据需求,您的解决方案可能看起来很简单,如下所示

store.dispatch({ type: 'SHOW_NOTIFICATION', text: 'You logged in.' })
setTimeout(() => {
  store.dispatch({ type: 'HIDE_NOTIFICATION' })
}, 5000)

除非你知道自己为什么要这么做,否则别担心.

Javascript相关问答推荐

Flutter:显示PDF和视频,但阻止下载

事件错误:类型错误:无法读取未定义的属性(读取stopPropagation)

如何使用CSS和JavaScript创建粘性、凝聚力的形状到形状(容器)变形?

在贝塞尔曲线的直线上找不到交叉点:(使用@Pomax的bezier.js)

字节数组通过echo框架传输到JS blob

类型脚本中只有字符串或数字键而不是符号键的对象

调用removeEvents不起作用

使用javascript将Plotly Expandable Sparkline转换为HighCharter Plot在bslib卡中

当Redux提供程序访问Reduxstore 时,可以安全地从Redux提供程序外部调用钩子?

try 使用javascript隐藏下拉 Select

未加载css colored颜色 ,无法将div设置为可见和不可见

为什么客户端没有收到来自服务器的响应消息?

如何 for each 输入动态设置输入变更值

对具有相似属性的对象数组进行分组,并使用串连的值获得结果

使用Reaction窗体挂钩注册日历组件

构建器模式与参数对象输入

FindByIdAndUpdate在嵌套对象中创建_id

正则表达式以确定给定文本是否不只包含邮箱字符串

限制数组中每个元素的长度,

Firefox的绝对定位没有达到预期效果