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

我没有运气使用setTimeout和返回另一个动作,无法找到这是如何在线完成的。所以任何建议都是欢迎的。


当前回答

Redux本身是一个非常冗长的库,对于这样的东西,你必须使用像Redux-thunk这样的东西,它会提供一个分派函数,所以你将能够在几秒钟后分派关闭通知。

我已经创建了一个库来解决诸如冗长性和可组合性等问题,您的示例将如下所示:

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],
});

因此,我们在异步动作中组合同步动作来显示通知,这可以请求一些后台信息,或者稍后检查通知是否被手动关闭。

其他回答

你可以用redux-thunk做到这一点。redux文档中有关于setTimeout等异步操作的指南。

我知道这个问题有点老了,但我将介绍另一种解决方案,使用还原可观测。史诗。

引用官方文件:

什么是可还原可观察?

基于RxJS 5的Redux中间件。合成和取消异步操作 创造副作用等等。

史诗是还原可观察的核心原语。

它是一个接受操作流并返回流的函数 的行动。行动进,行动出。

简而言之,您可以创建一个通过流接收操作的函数,然后返回一个新的操作流(使用常见的副作用,如超时、延迟、间隔和请求)。

让我发布代码,然后再详细解释一下

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)

解决这个问题的关键代码就像你看到的一样简单,唯一与其他答案不同的是rootpic函数。

点1。与saga一样,为了获得一个顶级函数来接收动作流并返回动作流,您必须将这些epics组合在一起,因此可以将它与中间件工厂createEpicMiddleware一起使用。在我们的例子中,我们只需要一个,所以我们只有rootEpic,所以我们不需要组合任何东西,但知道事实是很好的。

点2。我们的rootEpic只需要5行代码就可以处理副作用,这太棒了!包括这几乎是声明性的事实!

点3。逐行解析(在注释中)

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;
}

我希望这能有所帮助!

为什么这么难呢?这只是UI逻辑。使用专用动作设置通知数据:

dispatch({ notificationData: { message: 'message', expire: +new Date() + 5*1000 } })

和一个专用的组件来显示它:

const Notifications = ({ notificationData }) => {
    if(notificationData.expire > this.state.currentTime) {
      return <div>{notificationData.message}</div>
    } else return null;
}

在这种情况下,问题应该是“如何清理旧状态?”,“如何通知组件时间已更改”。

您可以实现一些TIMEOUT动作,该动作在组件的setTimeout上分派。

也许在显示新通知时清理它就可以了。

总之,应该有一些setTimeout,对吧?为什么不在组件中实现呢

setTimeout(() => this.setState({ currentTime: +new Date()}), 
           this.props.notificationData.expire-(+new Date()) )

其动机是“通知淡出”功能实际上是一个UI关注点。因此,它简化了业务逻辑的测试。

测试它是如何实现的似乎没有意义。只有验证通知何时应该超时才有意义。因此,更少的存根代码,更快的测试,更干净的代码。

Redux本身是一个非常冗长的库,对于这样的东西,你必须使用像Redux-thunk这样的东西,它会提供一个分派函数,所以你将能够在几秒钟后分派关闭通知。

我已经创建了一个库来解决诸如冗长性和可组合性等问题,您的示例将如下所示:

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],
});

因此,我们在异步动作中组合同步动作来显示通知,这可以请求一些后台信息,或者稍后检查通知是否被手动关闭。

带有示例项目的存储库

目前有四个样本项目:

内联编写异步代码 提取异步操作创建器 使用Redux坦克 使用Redux Saga

公认的答案是棒极了。

但这里缺少了一些东西:

没有可运行的示例项目,只有一些代码片段。 没有其他替代方案的示例代码,例如: 回家的故事

所以我创建了Hello Async存储库来添加缺少的东西:

可运行的项目。您可以下载并运行它们而无需修改。 提供更多替代方案的示例代码: 回家的故事 回来的循环 ...

回家的故事

接受的答案已经提供了异步代码内联,异步动作生成器和Redux坦克的示例代码片段。为了完整起见,我提供了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

saga基于ES6 Generators

// index.js

import createSagaMiddleware from 'redux-saga'
import saga from './sagas'

const sagaMiddleware = createSagaMiddleware()

const store = createStore(
  reducer,
  applyMiddleware(sagaMiddleware)
)

sagaMiddleware.run(saga)

与Redux坦克相比

Pros

你不会去回试的地狱。 您可以轻松地测试异步流。 你的行为保持纯净。

Cons

它依赖于相对较新的ES6 Generators。

如果上面的代码片段不能回答您的所有问题,请参考可运行项目。