如何使用超时调度Redux操作?

伊利亚:

我有一个操作可以更新我的应用程序的通知状态。通常,此通知将是错误或某种信息。然后,我需要在5秒钟后调度另一个动作,该动作会将通知状态恢复为初始状态,因此没有通知。这背后的主要原因是提供了5秒钟后通知自动消失的功能。

我在使用setTimeout并返回其他动作方面没有运气,也找不到在线完成此操作的方法。因此,欢迎提出任何建议。

和阿布拉莫夫:

不要陷入认为图书馆应该规定如何做所有事情陷阱如果您想在JavaScript中执行超时操作,则需要使用setTimeout没有理由为什么Redux动作应该有所不同。

Redux 确实提供了一些处理异步内容的替代方法,但是只有在意识到重复太多代码时,才应使用这些方法。除非您有此问题,否则请使用语言提供的内容并寻求最简单的解决方案。

内联编写异步代码

到目前为止,这是最简单的方法。这里没有关于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()或特定的动作创建者作为道具注入。但这对我们没有任何影响。

如果您不喜欢在从不同组件分派相同的动作时打错字,那么您可能希望提取动作创建者,而不是内联地分配动作对象:

// 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.')    

这看起来更简单,但是我们不建议您使用这种方法我们不喜欢它的主要原因是因为它迫使存储为单例这使得实现服务器渲染非常困难在服务器上,您将希望每个请求都有自己的存储,以便不同的用户获得不同的预加载数据。

单例存储也使测试更加困难。在测试操作创建者时,您不再可以模拟商店,因为他们引用了从特定模块导出的特定实际商店。您甚至无法从外部重置其状态。

因此,尽管从技术上讲您可以从模块中导出单例存储,但我们不建议这样做。除非您确定您的应用程序永远不会添加服务器渲染,否则请不要这样做。

回到以前的版本:

// 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这使得分隔容器组件和表示性组件变得更加棘手,因为以上述方式异步调度Redux动作的任何组件都必须接受dispatch作为道具,才能进一步传递它。您不能再绑定动作创建者了connect(),因为showNotificationWithTimeout()它并不是真正的动作创建者。它不返回Redux操作。

此外,记住哪个函数是同步动作创建者之类showNotification(),哪些是异步帮助器之类,可能会很尴尬showNotificationWithTimeout()您必须以不同的方式使用它们,并注意不要将它们彼此误认为。

这是寻找一种方法来“合法化”提供dispatch给助手功能的这种模式的动机,并帮助Redux将此类异步动作创建者“视为”正常动作创建者的特例,而不是完全不同的功能。

如果您仍然与我们在一起,并且还认为您的应用程序存在问题,欢迎您使用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)
})

启用此中间件后,如果您调度一个函数,则Redux Thunk中间件将为其dispatch提供一个参数。它还会“吞噬”这样的动作,因此不必担心您的reducer会收到奇怪的函数参数。减速器将仅接收普通对象操作-直接发出或由我们刚刚描述的函数发出。

这看起来不是很有用,是吗?不在这种特殊情况下。但是,它使我们可以声明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)

我们正在调用异步动作创建者来获取仅需要的内部函数,dispatch然后我们通过dispatch

但是,这比原始版本更尴尬!我们为什么还要那样走?

因为我之前告诉过你 如果启用了Redux Thunk中间件,则任何时候您尝试分派一个函数而不是一个操作对象时,中间件都将使用dispatch方法本身作为第一个参数调用该函数

因此,我们可以改为:

// component.js
this.props.dispatch(showNotificationWithTimeout('You just logged in.'))

最后,分派异步操作(实际上是一系列操作)看起来与向组件同步分派单个操作没有什么不同。这很好,因为组件不必关心是同步还是异步发生。我们只是将其抽象出来。

请注意,由于我们“教” Redux来识别此类“特殊”动作创建者(我们称它们为“ 笨拙的动作”创建者),因此我们现在可以在将使用常规动作创建者的任何地方使用它们。例如,我们可以将它们用于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)

暴徒的阅读状态

通常,减速器包含用于确定下一个状态的业务逻辑。但是,减速器仅在分派动作后才启动。如果您在重击动作创建者中有副作用(例如调用API),并且想要在某种情况下避免这种情况该怎么办?

无需使用笨拙的中间件,您只需在组件内部进行以下检查:

// component.js
if (this.props.areNotificationsEnabled) {
  showNotificationWithTimeout(this.props.dispatch, 'You just logged in.')
}

但是,提取动作创建者的目的是将重复性逻辑集中在许多组件上。幸运的是,Redux Thunk为您提供了一种读取 Redux存储库当前状态的方法。除之外dispatch,它还getState作为第二个参数传递给您从重击动作创建者返回的函数。这使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()仅用于有条件地分派不同的操作,请考虑将业务逻辑放入简化器中。

下一步

现在,您已经了解了thunk的工作原理,请查看使用它们的Redux 异步示例

您可能会发现许多示例,其中thunk返回Promises。这不是必需的,但可能非常方便。Redux不在乎您从thunk返回的内容,但会为您提供的返回值dispatch()这就是为什么您可以从thunk中返回Promise并通过调用来等待其完成的原因dispatch(someThunkReturningPromise()).then(...)

您也可以将复杂的重击动作创建者拆分为几个较小的重击动作创建者。dispatchthunk提供方法本身可以接受thunk,因此您可以递归应用模式。同样,这对于Promises最为有效,因为您可以在此之上实现异步控制流。

对于某些应用程序,您可能会发现自己的异步控制流需求过于复杂而无法用笨拙的东西来表达。例如,以这种方式编写时,重试失败的请求,使用令牌的重新授权流程或分步入职可能太冗长且容易出错。在这种情况下,您可能希望查看更高级的异步控制流解决方案,例如Redux SagaRedux Loop评估它们,比较与您的需求相关的示例,然后选择最喜欢的示例。

最后,如果您没有真正的需求,请不要使用任何东西(包括音乐)。请记住,根据要求,您的解决方案可能看起来像

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

除非您知道为什么要这么做,否则不要流汗。

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

如何使用超时调度Redux操作?

如何使用connect w / Redux从this.props获取简单的调度?

如何在Java中使用超时调用某些阻塞方法?

等待redux操作完成调度

如何从不了解Redux的服务中调度操作?

如何获得调度Redux

Redux Promise中间件如何反应将结果操作发送到调度?

React + Redux:当连接的组件加载时调用调度

如何将简单的Formik表单与Redux存储连接并调度操作?

在Redux中调度清除状态操作

调度Redux操作是否被视为昂贵?

无法在Typescript中调度redux操作

使用Redux-Thunk / Axios从onUploadProgress事件调度操作

如何使用Redux使用设置超时

如何使用axios使用redux-thunk进行调度

React操作如何访问调度?

Redux-未明确调用时调度操作

如何使用sagas在即将执行其他操作时调度操作?

Redux / React:在存储上映射时调度动作

如何响应Redux中的状态更改并调度其他操作?

使用 Kivy 调度操作

React & Redux:调度操作不起作用

如何修复“错误:Reducers 可能无法调度操作”。在我的 ReactJS Redux 应用程序中?

使用redux-offline时如何添加要调度的操作而不是有效的url

我如何从类而不是组件调度 redux 操作

如何使用 OMP_SCHEDULE 环境变量覆盖 OpenMp 运行时调度策略?

如何使用 redux 通过调度传递我的对象数组

使用多个参数进行调度并使用 Redux Toolkit 创建操作

在调度操作之前调用 API - Redux Toolkit