【问题标题】:Can I dispatch an action in reducer?我可以在减速器中调度一个动作吗?
【发布时间】:2016-08-12 08:44:12
【问题描述】:

是否可以在reducer 本身中调度一个动作?我有一个进度条和一个音频元素。目标是在音频元素中的时间更新时更新进度条。但是我不知道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?看起来这不应该是状态。
  • 它是一个 ES6 普通类(无反应),持有一个音频对象。如果它不会处于该状态,我将如何控制播放/停止、跳过等?
  • 你可能想看看 redux saga

标签: reactjs redux reducers


【解决方案1】:

在你的 reducer 完成之前启动另一个 dispatch 是一种反模式,因为当你的 reducer 完成时,你在 reducer 开始时收到的状态将不再是当前的应用程序状态。但是在 reducer 中调度另一个调度并不是一种反模式。事实上,这就是 Elm 语言所做的事情,正如你所知,Redux 试图将 Elm 架构引入 JavaScript。

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

// This middleware will just add the property "async dispatch" to all actions
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;
};

现在你的 reducer 可以这样做了:

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

【讨论】:

  • Marcelo,您在这里的博客文章很好地描述了您的方法的情况,所以我在这里链接到它:lazamar.github.io/dispatching-from-inside-of-reducers
  • 这正是我所需要的,除了中间件原样中断dispatch 应该返回操作。我将最后几行改为:const res = next(actionWithAsyncDispatch); syncActivityFinished = true; flushQueue(); return res;,效果很好。
  • 如果你不应该在 reducer 中调度一个动作,那么 redux-thunk 是否违反了 redux 的规则?
  • 当您尝试处理 websocket 响应时,这是如何工作的?这是我的减速器导出默认值 (state, action) => { const m2 = [ ...state.messages, action.payload ] return Object.assign({}, state, { messages: m2, }) } 我仍然收到此错误“在调度之间检测到状态突变”
【解决方案2】:

在 reducer 中调度动作是一种反模式。您的 reducer 应该没有副作用,只需消化操作负载并返回一个新的状态对象。在 reducer 中添加侦听器和调度操作可能会导致链接操作和其他副作用。

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

您可以在新的 React 组件中初始化 AudioElement 类对象,也可以将该类转换为 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 方法中调用它。或者还有其他方法可以获取调度程序。也许有道具?谢谢
  • 没问题。您可以将操作从connected 和react-redux 的父容器传递给MyAudioPlayer 组件。在此处查看如何使用mapDispatchToProps 执行此操作:github.com/reactjs/react-redux/blob/master/docs/…
  • 你的例子中定义的符号updateProgressAction在哪里?
  • 如果你不应该在 reducer 中调度一个动作,那么 redux-thunk 是否违反了 redux 的规则?
  • @EricWiener 我相信redux-thunk 是从另一个动作而不是reducer 分派一个动作。 stackoverflow.com/questions/35411423/…
【解决方案3】:

您可以尝试使用像 redux-saga 这样的库。它允许以一种非常干净的方式对异步函数进行排序、触发操作、使用延迟等等。非常强大!

【讨论】:

  • 你能指定如何使用redux-saga实现'在reducer中调度另一个调度'吗?
  • @XPD 你能解释一下你想要完成什么吗?如果您尝试使用 reducer 操作来调度另一个操作,那么您将无法没有类似于 redux-saga 的东西。
  • 例如,假设您有一个项目商店,您在其中加载了部分项目。物品被懒惰地加载。假设一个项目有一个供应商。供应商也懒洋洋地加载。所以在这种情况下,可能有一个供应商尚未加载的项目。在 item reducer 中,如果我们需要获取尚未加载的 item 的信息,我们必须通过 reducer 再次从服务器加载数据。在这种情况下,redux-saga 如何在 reducer 中提供帮助?
  • 好的,假设您想在用户尝试访问item 详细信息页面时触发对supplier 信息的请求。你的componentDidMount() 会触发一个调度动作的函数,比如FETCH_SUPPLIER。在 reducer 中,您可以勾选 loading: true 之类的内容,以在发出请求时显示微调器。 redux-saga 将侦听该操​​作,并作为响应触发实际请求。利用生成器函数,它可以等待响应并将其转储到FETCH_SUPPLIER_SUCCEEDED。然后 reducer 用供应商信息更新 store。
【解决方案4】:

redux-loop 从 Elm 那里得到启发并提供了这种模式。

【讨论】:

    【解决方案5】:

    reducer 内部的调度和操作似乎发生错误。

    我使用useReducer 做了一个简单的反例,其中调度了“INCREASE”,然后调度了“SUB”。

    在示例中,我预计会发送“INCREASE”,然后发送“SUB”,然后将 cnt 设置为 -1,然后 继续“INCREASE”操作将cnt设置为0,但它是-1(“INCREASE”被忽略)

    看到这个: https://codesandbox.io/s/simple-react-context-example-forked-p7po7?file=/src/index.js:144-154

    let listener = () => {
      console.log("test");
    };
    const middleware = (action) => {
      console.log(action);
      if (action.type === "INCREASE") {
        listener();
      }
    };
    
    const counterReducer = (state, action) => {
      middleware(action);
      switch (action.type) {
        case "INCREASE":
          return {
            ...state,
            cnt: state.cnt + action.payload
          };
        case "SUB":
          return {
            ...state,
            cnt: state.cnt - action.payload
          };
        default:
          return state;
      }
    };
    
    const Test = () => {
      const { cnt, increase, substract } = useContext(CounterContext);
    
      useEffect(() => {
        listener = substract;
      });
    
      return (
        <button
          onClick={() => {
            increase();
          }}
        >
          {cnt}
        </button>
      );
    };
    
    
    {type: "INCREASE", payload: 1}
    {type: "SUB", payload: 1}
    // expected: cnt: 0
    // cnt = -1
    

    【讨论】:

      猜你喜欢
      • 2021-01-13
      • 1970-01-01
      • 2019-08-13
      • 2017-03-24
      • 2016-02-09
      • 2021-08-07
      • 1970-01-01
      • 1970-01-01
      • 2021-10-22
      相关资源
      最近更新 更多