レデューサーでアクションをディスパッチできますか?


196

レデューサー自体でアクションをディスパッチすることは可能ですか?プログレスバーとオーディオ要素があります。目標は、オーディオ要素で時間が更新されたときにプログレスバーを更新することです。しかし、プログレスバーを更新するために、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

ES6プレーンクラス(反応なし)であり、オーディオオブジェクトを保持します。その状態にならない場合、再生/停止、スキップなどをどのように制御しますか?
klanm

2
redux sagaを調べてみてください
Kyeotic

回答:


150

レデューサー内でのアクションのディスパッチはアンチパターンです。レデューサーには副作用がなく、単にアクションペイロードをダイジェストして新しい状態オブジェクトを返す必要があります。リスナーを追加し、レデューサー内でアクションをディスパッチすると、アクションの連鎖やその他の副作用が発生する可能性があります。

初期化された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、あなたが直接派遣を呼び出す必要はありませんので。


説明をありがとうございます!しかし、私はまだディスパッチャーにアクセスする方法を知りません。私は常にreact-reduxのconnectメソッドを使用しました。しかし、私はupdateProgressメソッドでそれを呼び出す方法がわかりません。または、ディスパッチャーを取得する別の方法があります。多分小道具で?ありがとう
klanm

問題ない。MyAudioPlayerconnect編集された親コンテナからコンポーネントにアクションを渡すことができreact-reduxます。ことを行う方法を確認してくださいmapDispatchToPropsここ:github.com/reactjs/react-redux/blob/master/docs/...
ebuat3989

6
updateProgressActionあなたの例ではシンボルはどこに定義されていますか?
Charles Prakash Dasari

2
レデューサー内でアクションをディスパッチすることになっていない場合、redux-thunkはreduxのルールに違反していますか?
エリックウィナー

2
@EricWienerリデューサーredux-thunkではなく、別のアクションからアクションをディスパッチしていると思います。stackoverflow.com/questions/35411423/…–
sallf

159

レデューサーが完了する前に別のディスパッチを開始するのはアンチパターンです。これは、レデューサーの開始時に受け取った状態が、レデューサーが完了すると現在のアプリケーション状態ではなくなるためです。ただし、レデューサー内から別のディスパッチをスケジュールすることは、アンチパターンではありません。実際、それが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
マルセロ、あなたのブログ投稿はあなたのアプローチの状況を説明する素晴らしい仕事をしているので、ここにリンクします:lazamar.github.io/dispatching-from-inside-of-reducers
Clayton

3
これは、ミドルウェアの現状のままのブレークdispatchを除いて、私が必要とするものでした。最後の行を次のように変更しましたconst res = next(actionWithAsyncDispatch); syncActivityFinished = true; flushQueue(); return res;
zanerock

1
レデューサー内でアクションをディスパッチすることになっていない場合、redux-thunkはreduxのルールに違反していますか?
エリックウィナー

WebSocket応答を処理しようとすると、これはどのように機能しますか?これは私のレデューサーエクスポートのデフォルトです(状態、アクション)=> {const m2 = [... state.messages、action.payload] return Object.assign({}、state、{messages:m2、})}そして私はまだこのエラーが発生する "ディスパッチの間に状態の変異が検出されました"
Quantumpotato

12

redux-sagaのようなライブラリを使用してみてください。これにより、非同期関数のシーケンス、アクションの開始、遅延の使用などの非常にクリーンな方法が可能になります。とてもパワフルです!


1
redux-sagaで「レデューサー内で別のディスパッチのスケジュールを設定する」方法を指定できますか?
XPD

1
@XPDあなたが達成したいことをもう少し説明できますか?レデューサーアクションを使用して別のアクションをディスパッチしようとしている場合、redux-sagaに似たものがなければできません。
chandlervdw

1
たとえば、アイテムの一部をロードしたアイテムストアがあるとします。アイテムは遅延読み込みされます。アイテムにサプライヤーがあると仮定します。サプライヤーも遅延読み込み。したがって、この場合、そのサプライヤーがロードしていないアイテムがある可能性があります。アイテムレデューサーで、まだロードされていないアイテムに関する情報を取得する必要がある場合は、レデューサーを介してサーバーからデータを再度ロードする必要があります。その場合、redux-sagaはレデューサーの内部でどのように役立ちますか?
XPD 2017

1
ではsupplier、ユーザーがitem詳細ページにアクセスしようとしたときに、この情報リクエストを開始したいとします。あなたのcomponentDidMount()アクションをディスパッチする機能をオフに解雇だろう、と言いますFETCH_SUPPLIER。レデューサー内ではloading: true、リクエストが行われている間にスピナーを表示するためにのようなものにチェックを付けることができます。 redux-sagaそのアクションをリッスンし、それに応じて実際のリクエストを開始します。ジェネレータ関数を利用して、応答を待ってにダンプできFETCH_SUPPLIER_SUCCEEDEDます。次に、レデューサーはストアをサプライヤー情報で更新します。
chandlervdw 2017

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.