Czy mogę wysłać akcję do reduktora?

196

czy możliwe jest wysłanie akcji do samego reduktora? Mam pasek postępu i element audio. Celem jest aktualizacja paska postępu, gdy czas zostanie zaktualizowany w elemencie audio. Ale nie wiem, gdzie umieścić moduł obsługi zdarzeń ontimeupdate ani jak wysłać akcji w wywołaniu zwrotnym programu ontimeupdate, aby zaktualizować pasek postępu. Oto mój kod:

//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;
klanm
źródło
Co to jest AudioElement? Wygląda na to, że to nie powinno być coś w porządku.
ebuat3989,
jest to zwykła klasa ES6 (brak reakcji), trzymająca obiekt audio. Jeśli nie byłoby w tym stanie, w jaki sposób kontrolowałbym odtwarzanie / zatrzymywanie, pomijanie itp.?
klanm 19.04.16
2
Możesz zajrzeć do sagi
redux

Odpowiedzi:

150

Wywołanie akcji w reduktorze jest anty-wzorem . Twój reduktor powinien być bez efektów ubocznych, po prostu trawiąc ładunek akcji i zwracając nowy obiekt stanu. Dodanie detektorów i wysłanie akcji do reduktora może prowadzić do powiązanych akcji i innych efektów ubocznych.

Wygląda na to, że zainicjowana AudioElementklasa i detektor zdarzeń należą do komponentu, a nie do stanu. W ramach detektora zdarzeń możesz wysłać akcję, która zostanie zaktualizowanaprogress w stanie.

Możesz zainicjować AudioElementobiekt klasy w nowym składniku React lub po prostu przekonwertować tę klasę na składnik 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);

Pamiętaj, że updateProgressActionjest on automatycznie pakowany, dispatchwięc nie musisz bezpośrednio dzwonić do wysyłki.

ebuat3989
źródło
dziękuję bardzo za wyjaśnienie! Ale wciąż nie wiem, jak uzyskać dostęp do dyspozytora. Zawsze korzystałem z metody Connect z React-Redux. ale nie wiem jak to nazwać metodą updateProgress. Czy jest inny sposób na uzyskanie dyspozytora. może z rekwizytami? dziękuję
klanm
Nie ma problemu. Możesz przekazać akcję do MyAudioPlayerkomponentu z kontenera nadrzędnego, który jest connectedytowany za pomocą react-redux. Sprawdź, jak to zrobić mapDispatchToPropstutaj: github.com/reactjs/react-redux/blob/master/docs/…
ebuat3989,
6
Gdzie jest updateProgressActionzdefiniowany symbol w twoim przykładzie?
Charles Prakash Dasari
2
Jeśli nie masz wykonywać akcji w reduktorze, to czy redux-thunk łamie zasady redux?
Eric Wiener,
2
@EricWiener Uważam, że redux-thunkwysyłanie akcji z innej akcji, a nie z reduktora. stackoverflow.com/questions/35411423/...
sallf 17.09.19
160

Rozpoczęcie kolejnej wysyłki przed ukończeniem reduktora jest anty-wzorcem , ponieważ stan, który otrzymałeś na początku reduktora, nie będzie już bieżącym stanem aplikacji po zakończeniu reduktora. Ale planowanie kolejnej wysyłki z poziomu reduktora NIE jest anty-wzorem . W rzeczywistości to właśnie robi język Elm, a jak wiadomo Redux jest próbą przeniesienia architektury Elm do JavaScript.

Oto oprogramowanie pośrednie, które doda właściwość asyncDispatchdo wszystkich twoich działań. Gdy twój reduktor zakończy działanie i zwróci nowy stan aplikacji, asyncDispatchuruchomi się store.dispatchz każdym działaniem, jakie mu podasz.

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

Teraz twój reduktor może to zrobić:

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 Lazaroni
źródło
7
Marcelo, twój post na blogu wykonuje świetną robotę, opisując okoliczności twojego podejścia, więc linkuję
Dejay Clayton
3
Właśnie tego potrzebowałem, z wyjątkiem przerw w oprogramowaniu pośrednim, dispatchktóre powinny zwrócić akcję. Zmieniłem ostatnie wiersze na: const res = next(actionWithAsyncDispatch); syncActivityFinished = true; flushQueue(); return res;i działało świetnie.
zanerock
1
Jeśli nie masz wykonywać akcji w reduktorze, to czy redux-thunk łamie zasady redux?
Eric Wiener,
Jak to działa, gdy próbujesz obsłużyć odpowiedzi WebSocket? To jest domyślny eksport mojego reduktora (stan, akcja) => {const m2 = [... state.messages, action.payload] return Object.assign ({}, state, {messages: m2,})} i I STILL otrzymaj ten błąd „wykryto mutację stanu między
wysyłkami
12

Możesz spróbować użyć biblioteki takiej jak redux-saga . Pozwala to na bardzo czysty sposób na sekwencjonowanie funkcji asynchronicznych, uruchamianie akcji, używanie opóźnień i wiele więcej. Jest bardzo potężny!

chandlervdw
źródło
1
czy możesz określić, jak osiągnąć „planowanie kolejnej wysyłki wewnątrz reduktora” dzięki redux-saga?
XPD,
1
@XPD czy możesz wyjaśnić nieco więcej, co chcesz osiągnąć? Jeśli próbujesz użyć akcji reduktora, aby wywołać inną akcję, nie będziesz w stanie tego zrobić bez czegoś podobnego do redux-sagi.
chandlervdw
1
Załóżmy na przykład, że masz sklep z przedmiotami, w którym załadowałeś część przedmiotów. Przedmioty są ładowane leniwie. Załóżmy, że element ma dostawcę. Dostawcy również ładowali się leniwie. W takim przypadku może istnieć element, którego dostawca nie został załadowany. W reduktorze przedmiotów, jeśli potrzebujemy uzyskać informacje o przedmiocie, który nie został jeszcze załadowany, musimy ponownie załadować dane z serwera przez reduktor. W takim razie w jaki sposób redux-saga pomaga wewnątrz reduktora?
XPD,
1
Ok, powiedzmy, że chcesz odpalić tę prośbę o supplierinformacje, gdy użytkownik próbuje odwiedzić itemstronę szczegółów. Twój componentDidMount()by wystrzelić funkcję, która wywołuje akcję, powiedzmy FETCH_SUPPLIER. W obrębie reduktora możesz zaznaczyć coś takiego jak a, loading: trueaby pokazać spinner podczas składania wniosku. redux-sagawysłucha tej akcji, aw odpowiedzi odpali faktyczne żądanie. Korzystając z funkcji generatora, może wtedy poczekać na odpowiedź i wrzucić ją do FETCH_SUPPLIER_SUCCEEDED. Reduktor następnie aktualizuje sklep o informacje o dostawcy.
chandlervdw
6

pętla redux pobiera sygnał z Wiązu i zapewnia ten wzorzec.

Quang Van
źródło