是否有可能在减速器本身调度一个动作?我有一个进度条和一个音频元素。目标是在音频元素中的时间更新时更新进度条。但是我不知道在哪里放置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类和事件监听器属于一个组件而不是状态。在事件侦听器中,您可以分派一个操作,该操作将更新状态中的进度。

你可以在一个新的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被自动封装在分派中,因此您不需要直接调用分派。


你可以尝试使用redux-saga这样的库。它允许一种非常干净的方式来排序异步函数,发射动作,使用延迟等等。这是非常强大的!


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

下面是一个中间件,它将向所有操作添加asyncDispatch属性。当您的reducer完成并返回新的应用程序状态时,asyncDispatch将触发存储。用你所能做的一切来调度它。

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

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


减速机内部的调度和动作似乎出现bug。

我用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

因为任何事情在技术上都是可能的,所以你可以做到。但是你不应该这样做。

以下是Dan Abramov (Redux的创造者)的一句话:

“你为什么要在减速机上调度?”这是严重 误用库。这和React不允许你做的完全一样 到setState内部渲染。

来自他自己创建的“禁止从减速机内部调度”的Github门票