我可以派遣一个减速器中的动作吗?


195

是否可以在减速器本身中调度动作?我有一个进度栏和一个音频元素。目的是在音频元素中的时间更新时更新进度条。但是我不知道在哪里放置ontimeupdate事件处理程序,或者如何在ontimeupdate的回调中分派动作来更新进度条。这是我的代码:

//reducer

const initialState = {
    audioElement: new AudioElement('test.mp3'),
    progress: 0.0
}

initialState.audioElement.audio.ontimeupdate = () => {
    console.log('progress', initialState.audioElement.currentTime/initialState.audioElement.duration);
    //how to dispatch 'SET_PROGRESS_VALUE' now?
};


const audio = (state=initialState, action) => {
    switch(action.type){
        case 'SET_PROGRESS_VALUE':
            return Object.assign({}, state, {progress: action.progress});
        default: return state;
    }

}

export default audio;

什么AudioElement啊 似乎这不应该成为现状。
ebuat3989 '16

这是一个ES6普通类(无反应),包含一个音频对象。如果不处于该状态,我将如何控制播放/停止,跳过等?
klanm

2
你可能想寻找到终极版传奇
Kyeotic

Answers:


150

在reducer内调度动作是一种反模式。减速器应该没有副作用,只需消化操作有效负载并返回新的状态对象即可。在Reducer中添加侦听器和调度动作可能导致连锁动作和其他副作用。

听起来像您的已初始化AudioElement类和事件侦听器属于组件而不是处于状态。在事件监听器中,您可以调度一个动作,该动作将更新progress在状态中。

您可以AudioElement在新的React组件中初始化类对象,也可以仅将该类转换为React组件。

class MyAudioPlayer extends React.Component {
  constructor(props) {
    super(props);

    this.player = new AudioElement('test.mp3');

    this.player.audio.ontimeupdate = this.updateProgress;
  }

  updateProgress () {
    // Dispatch action to reducer with updated progress.
    // You might want to actually send the current time and do the
    // calculation from within the reducer.
    this.props.updateProgressAction();
  }

  render () {
    // Render the audio player controls, progress bar, whatever else
    return <p>Progress: {this.props.progress}</p>;
  }
}

class MyContainer extends React.Component {
   render() {
     return <MyAudioPlayer updateProgress={this.props.updateProgress} />
   }
}

function mapStateToProps (state) { return {}; }

return connect(mapStateToProps, {
  updateProgressAction
})(MyContainer);

请注意,会updateProgressAction被自动包装,dispatch因此您无需直接调用dispatch。


非常感谢您的澄清!但是我仍然不知道如何访问调度程序。我总是使用react-redux的connect方法。但是我不知道如何在updateProgress方法中调用它。还是有另一种获取调度程序的方法。也许有道具?谢谢
klanm '16

没问题。您可以将操作MyAudioPlayerconnect附带的父容器传递到组件react-redux。看看如何做到这一点用mapDispatchToProps在这里:github.com/reactjs/react-redux/blob/master/docs/...
ebuat3989

6
updateProgressAction您的示例中的符号定义在哪里?
查尔斯·普拉卡什·达萨里

2
如果您不应该在reducer中调度动作,那么redux-thunk是否违反了redux的规则?
埃里克·维纳

2
我相信@EricWiener redux-thunk正在调度另一个动作(而不是reducer)中的一个动作。stackoverflow.com/questions/35411423/…–
Sallf

158

在reducer完成之前启动另一个调度是一种反模式,因为在reducer完成时,您在reducer开头收到的状态将不再是当前应用程序状态。但是从reducer内调度另一个调度不是反模式。实际上,这就是Elm语言的作用,众所周知,Redux试图将Elm架构引入JavaScript。

这是一个将属性添加asyncDispatch到所有操作的中间件。减速器完成并返回新的应用程序状态后,asyncDispatch将以store.dispatch您对其执行的任何操作触发。

// This middleware will just add the property "async dispatch"
// to actions with the "async" propperty set to true
const asyncDispatchMiddleware = store => next => action => {
  let syncActivityFinished = false;
  let actionQueue = [];

  function flushQueue() {
    actionQueue.forEach(a => store.dispatch(a)); // flush queue
    actionQueue = [];
  }

  function asyncDispatch(asyncAction) {
    actionQueue = actionQueue.concat([asyncAction]);

    if (syncActivityFinished) {
      flushQueue();
    }
  }

  const actionWithAsyncDispatch =
    Object.assign({}, action, { asyncDispatch });

  const res = next(actionWithAsyncDispatch);

  syncActivityFinished = true;
  flushQueue();

  return res;
};

现在,您的减速器可以执行以下操作:

function reducer(state, action) {
  switch (action.type) {
    case "fetch-start":
      fetch('wwww.example.com')
        .then(r => r.json())
        .then(r => action.asyncDispatch({ type: "fetch-response", value: r }))
      return state;

    case "fetch-response":
      return Object.assign({}, state, { whatever: action.value });;
  }
}

7
Marcelo,您的博客文章在描述您的方法的情况方面做得很好,所以我在这里链接到它:lazamar.github.io/dispatching-from-inside-of-reducers
Clayton

3
这正是我需要的,除了中间件原样的中断dispatch应返回操作。我将最后几行更改为:const res = next(actionWithAsyncDispatch); syncActivityFinished = true; flushQueue(); return res;效果很好。
zanerock

1
如果您不应该在reducer中调度动作,那么redux-thunk是否违反了redux的规则?
埃里克·维纳

当您尝试处理websocket响应时,这如何工作?这是我的reducer导出默认值(状态,操作)=> {const m2 = [... state.messages,action.payload] return Object.assign({},state,{消息:m2,})}并且我仍然得到这个错误“在调度之间检测到状态突变”
–quantumpotato

12

您可以尝试使用redux-saga之类的库。它提供了一种非常干净的方式来排序异步功能,触发动作,使用延迟等。它非常强大!


1
您可以指定如何使用redux-saga实现“在reducer内调度另一个调度”?
XPD

1
@XPD您能解释一下您想要完成的事情吗?如果您尝试使用reducer动作来分派另一个动作,那么就没有类似redux-saga的东西。
chandlervdw

1
例如,假设您有一个项目存储,其中已加载了部分项目。延迟加载项目。假设一个项目有一个供应商。供应商也懒洋洋地装载。因此,在这种情况下,可能有一个尚未加载其供应商的商品。在项目精简器中,如果我们需要获取尚未加载的项目的信息,则必须通过精简器再次从服务器加载数据。在这种情况下,redux-saga对减速器内部有何帮助?
XPD

1
好的,假设您想supplier在用户尝试访问item详细信息页面时触发此信息请求。您componentDidMount()会触发一个调度动作的函数,例如FETCH_SUPPLIER。在化简器中,您可以loading: true在发出请求时在诸如a的位置打钩以显示微调器。 redux-saga会监听该操作,并作为响应,触发实际请求。然后,利用生成器函数,它可以等待响应并将其转储到中FETCH_SUPPLIER_SUCCEEDED。然后,reducer用供应商信息更新商店。
chandlervdw

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.