javascript - 如何在超时时分派(dispatch) Redux 操作?

标签 javascript redux react-redux timeout

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

我没有运气使用 setTimeout并返回另一个操作,但无法在网上找到这是如何完成的。因此,欢迎任何建议。

最佳答案

不要落入trap of thinking a library should prescribe how to do everything .如果你想在 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()或作为 Prop 注入(inject)的特定 Action 创建者。然而,这对我们来说没有任何区别。

如果您不喜欢在从不同组件分派(dispatch)相同操作时犯错,您可能希望提取操作创建者而不是内联分派(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)

或者,如果您之前已将它们绑定(bind)到 connect() :
this.props.showNotification('You just logged in.')
setTimeout(() => {
  this.props.hideNotification()
}, 5000)

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

提取 Async Action Creator

上述方法在简单情况下工作正常,但您可能会发现它有一些问题:
  • 它迫使您在任何想要显示通知的地方复制此逻辑。
  • 通知没有 ID,因此如果您足够快地显示两个通知,您就会遇到竞争条件。当第一次超时完成时,它将调度 HIDE_NOTIFICATION , 错误地比超时后更早地隐藏了第二个通知。

  • 要解决这些问题,您需要提取一个函数来集中超时逻辑并分派(dispatch)这两个操作。它可能看起来像这样:
    // 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但是因为我们想要一个外部函数来控制调度,我们需要让它控制调度。

    如果你有一个从某个模块导出的单例存储,你可以只导入它和 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.')    
    

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

    单例存储也使测试更加困难。在测试 Action 创建器时,您不能再模拟商店,因为它们引用从特定模块导出的特定真实商店。你甚至不能从外部重置它的状态。

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

    回到之前的版本:
    // 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作为 Prop ,以便它可以进一步传递。你不能只用 connect() 绑定(bind) Action 创建者不再是因为 showNotificationWithTimeout()不是真正的 Action 创建者。它不返回 Redux 操作。

    此外,记住哪些函数是同步 Action 创建者(如 showNotification())可能会很尴尬。以及像 showNotificationWithTimeout() 这样的异步助手.您必须以不同的方式使用它们,并小心不要将它们相互混淆。

    这就是 的动机找到一种方法使这种提供模式“合法化”dispatch到一个辅助函数,并帮助 Redux “看到”这样的异步 Action 创建者作为正常 Action 创建者的特例 而不是完全不同的功能。

    如果您仍然与我们在一起并且您也认为您的应用程序存在问题,欢迎您使用 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作为论据。它也会“吞下”这样的 Action ,所以不用担心你的 reducer 会收到奇怪的函数参数。你的 reducer 只会接收普通的对象 Action ——直接发出,或者由我们刚刚描述的函数发出。

    这看起来不是很有用,是吗?不是在这种特殊情况下。但是它让我们声明 showNotificationWithTimeout()作为一个普通的 Redux Action 创建者:
    // 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.'))
    

    最后,分派(dispatch)异步操作(实际上是一系列操作)与将单个操作同步分派(dispatch)到组件看起来没有什么不同。这很好,因为组件不应该关心某些事情是同步发生还是异步发生。我们只是把它抽象出来。

    请注意,由于我们“教”了 Redux 识别此类“特殊” Action 创建者(我们称它们为 thunk Action 创建者),因此我们现在可以在任何需要使用常规 Action 创建者的地方使用它们。例如,我们可以将它们与 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 中的读取状态

    通常,您的 reducer 包含用于确定下一个状态的业务逻辑。然而,reducer 只在 Action 被分派(dispatch)后才开始。如果您在 thunk Action 创建器中有副作用(例如调用 API),并且您想在某些情况下阻止它怎么办?

    不使用 thunk 中间件,您只需在组件内部进行此检查:
    // component.js
    if (this.props.areNotificationsEnabled) {
      showNotificationWithTimeout(this.props.dispatch, 'You just logged in.')
    }
    

    然而,提取 Action 创建者的目的是将这种重复的逻辑集中在许多组件中。幸运的是,Redux Thunk 为您提供了一种读取 Redux 存储当前状态的方法。除了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()只是为了有条件地分派(dispatch)不同的 Action ,请考虑将业务逻辑放入 reducer 中。

    下一步

    既然您对 thunk 的工作原理有了基本的了解,请查看 Redux async example使用它们。

    你可能会发现很多 thunk 返回 Promise 的例子。这不是必需的,但可能非常方便。 Redux 并不关心你从 thunk 返回什么,但它会从 dispatch() 给你它的返回值。 .这就是为什么你可以从 thunk 返回一个 Promise 并通过调用 dispatch(someThunkReturningPromise()).then(...) 等待它完成的原因。 .

    您还可以将复杂的 thunk Action 创建器拆分为几个较小的 thunk Action 创建器。 dispatch thunk 提供的方法可以接受 thunk 本身,因此您可以递归地应用该模式。同样,这对 Promise 最有效,因为您可以在其之上实现异步控制流。

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

    最后,如果你真的不需要它们,不要使用任何东西(包括 thunk)。请记住,根据要求,您的解决方案可能看起来很简单
    store.dispatch({ type: 'SHOW_NOTIFICATION', text: 'You logged in.' })
    setTimeout(() => {
      store.dispatch({ type: 'HIDE_NOTIFICATION' })
    }, 5000)
    

    除非你知道你为什么这样做,否则不要出汗。

    关于javascript - 如何在超时时分派(dispatch) Redux 操作?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35411423/

    相关文章:

    javascript - 如何操作 append 内容? jQuery

    reactjs - 何时以及如何在react/router/redux应用程序中获取详细 View 的数据?

    javascript - NGXS 在 setState 中传播状态

    javascript - Redux saga,如何从组件中监听一个 Action

    javascript - Redux 存储中的确定性 API 调用结果

    javascript - redux dumb组件功能单元测试

    javascript - 使用 React 功能组件重定向

    javascript - jquery延迟文档准备好功能的秒数?

    javascript - 如何在选择日期后关闭日期时间选择器?

    javascript - 将用户/计算机分数添加到推文