我可以在减速器中发送一个动作吗?

IT技术 reactjs redux reducers
2021-03-29 02:32:09

是否可以在减速器本身中分派动作?我有一个进度条和一个音频元素。目标是在音频元素中更新时间时更新进度条。但是我不知道在哪里放置 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;
5个回答

在您的减速器完成之前启动另一个分派是一种反模式,因为当减速器完成时,您在减速器开始时收到的状态将不再是当前的应用程序状态。但是从减速器内部调度另一个调度不是反模式事实上,这就是 Elm 语言所做的,正如您所知,Redux 是将 Elm 架构引入 JavaScript 的尝试。

这是一个中间件,它将将该属性添加asyncDispatch到您的所有操作中。当您的减速器完成并返回新的应用程序状态时,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;
};

现在你的减速机可以做到这一点:

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

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

听起来像您的初始化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。

没问题。您可以将操作MyAudioPlayer从使用connected的父容器传递给组件react-reduxmapDispatchToProps在此处查看如何执行此操作:github.com/reactjs/react-redux/blob/master/docs/...
2021-06-08 02:32:09
非常感谢您的澄清!但是我仍然不知道如何访问调度程序。我总是使用 react-redux 中的 connect 方法。但我不知道如何在 updateProgress 方法中调用它。或者还有其他方法可以获取调度员。也许有道具?谢谢
2021-06-09 02:32:09
updateProgressAction您的示例中定义的符号哪里
2021-06-09 02:32:09
如果您不应该在 reducer 中调度 action,那么 redux-thunk 是否违反了 redux 的规则?
2021-06-13 02:32:09
@EricWiener 我相信redux-thunk是从另一个动作而不是减速器分派动作。stackoverflow.com/questions/35411423/...
2021-06-19 02:32:09

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

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

redux-loop 借鉴了 Elm 并提供了这种模式。

减速器内部的调度和动作似乎发生了错误。

我做了一个简单的反例,使用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