Posso despachar uma ação no redutor?


196

é possível despachar uma ação no próprio redutor? Eu tenho uma barra de progresso e um elemento de áudio. O objetivo é atualizar a barra de progresso quando a hora for atualizada no elemento de áudio. Mas não sei onde colocar o manipulador de eventos ontimeupdate ou como despachar uma ação no retorno de chamada de ontimeupdate para atualizar a barra de progresso. Aqui está o meu código:

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

O que é AudioElement? Parece que isso não deveria ser algo no estado.
precisa saber é o seguinte

é uma classe simples do ES6 (sem reação), contendo um Objeto de Áudio. Se não estivesse no estado, como controlaria reproduzir / parar, pular etc.?
Klanm

2
Você pode querer olhar para a saga redux
Kyeotic

Respostas:


150

Despachar uma ação dentro de um redutor é um antipadrão . Seu redutor deve estar sem efeitos colaterais, simplesmente digerindo a carga útil da ação e retornando um novo objeto de estado. Adicionar ouvintes e enviar ações no redutor pode levar a ações encadeadas e outros efeitos colaterais.

Parece que sua AudioElementclasse inicializada e o ouvinte de eventos pertencem a um componente e não ao estado. No ouvinte de eventos, você pode despachar uma ação, que será atualizada progressno estado.

Você pode inicializar o AudioElementobjeto de classe em um novo componente React ou apenas converter essa classe em um componente 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);

Observe que ele updateProgressActioné automaticamente empacotado dispatchpara que você não precise ligar diretamente para o envio.


muito obrigado pelo esclarecimento! Mas ainda não sei como acessar o despachante. Eu sempre usei o método connect do react-redux. mas não sei como chamá-lo no método updateProgress. Ou existe outra maneira de obter o despachante. talvez com adereços? obrigado
klanm

Sem problemas. Você pode transmitir a ação ao MyAudioPlayercomponente do contêiner pai connecteditado comreact-redux . Veja como fazer isso mapDispatchToPropsaqui: github.com/reactjs/react-redux/blob/master/docs/…
ebuat3989:

6
Onde o símbolo está updateProgressActiondefinido no seu exemplo?
Charles Prakash Dasari

2
Se você não deve despachar uma ação dentro de um redutor, o redux-thunk está quebrando as regras do redux?
Eric Wiener

2
@EricWiener Eu acredito que redux-thunkestá despachando uma ação de outra ação, não o redutor. stackoverflow.com/questions/35411423/…
sallf 17/09/19

159

Iniciar outro despacho antes que o redutor seja concluído é um antipadrão , porque o estado que você recebeu no início do redutor não será mais o estado atual do aplicativo quando o redutor terminar. Mas agendar outro despacho de dentro de um redutor NÃO é um antipadrão . De fato, é isso que a linguagem Elm faz e, como você sabe, o Redux é uma tentativa de trazer a arquitetura Elm para o JavaScript.

Aqui está um middleware que adicionará a propriedade asyncDispatcha todas as suas ações. Quando o redutor terminar e retornar o novo estado do aplicativo, asyncDispatchserá acionado store.dispatchcom qualquer ação que você der a ele.

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

Agora seu redutor pode fazer isso:

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
Marcelo, seu post aqui faz um ótimo trabalho descrevendo as circunstâncias de sua abordagem, então eu estou ligando para ela aqui: lazamar.github.io/dispatching-from-inside-of-reducers
DeJay Clayton

3
Era exatamente isso que eu precisava, exceto as quebras do middleware dispatchque devem retornar a ação. Alterei as últimas linhas para: const res = next(actionWithAsyncDispatch); syncActivityFinished = true; flushQueue(); return res;e funcionou muito bem.
precisa saber é o seguinte

1
Se você não deve despachar uma ação dentro de um redutor, o redux-thunk está quebrando as regras do redux?
Eric Wiener

Como isso funciona quando você tenta lidar com as respostas do websocket? Esse é o padrão de exportação do meu redutor (estado, ação) => {const m2 = [... estado.mensagens, ação.payload] retorna Object.assign ({}, estado, {messages: m2,})} e EU AINDA se esse erro "mutação estado foi detectado em entre despachos"
quantumpotato

12

Você pode tentar usar uma biblioteca como redux-saga . Ele permite uma maneira muito limpa de sequenciar funções assíncronas, acionar ações, usar atrasos e muito mais. É muito poderoso!


1
você pode especificar como conseguir 'agendar outro despacho dentro do redutor' com o redux-saga?
XPD 16/12

1
@XPD, você pode explicar um pouco mais o que deseja realizar? Se você estiver tentando usar uma ação redutora para despachar outra ação, não poderá fazer isso sem algo semelhante à saga redux.
precisa

1
Por exemplo, suponha que você tenha um armazenamento de itens no qual você carregou uma parte dos itens. Os itens são carregados preguiçosamente. Suponha que um item tenha um fornecedor. Os fornecedores também carregaram preguiçosamente. Portanto, nesse caso, pode haver um item que seu fornecedor não tenha sido carregado. Em um redutor de itens, se precisarmos obter informações sobre um item que ainda não foi carregado, precisamos carregar os dados do servidor novamente através de um redutor. Nesse caso, como o redux-saga ajuda dentro de um redutor?
XPD

1
Ok, digamos que você deseje acionar essa solicitação de supplierinformações quando o usuário tentar acessar a itempágina de detalhes. Você componentDidMount()dispararia uma função que despacha uma ação, digamos FETCH_SUPPLIER. Dentro do redutor, você pode marcar algo como a loading: truepara mostrar um botão giratório enquanto a solicitação é feita. redux-sagaouviria essa ação e, em resposta, dispararia a solicitação real. Utilizando funções de gerador, ele pode esperar a resposta e despejá-la FETCH_SUPPLIER_SUCCEEDED. O redutor atualiza a loja com informações do fornecedor.
chandlervdw

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.