Могу ли я отправить действие в редуктор?


198

Можно ли отправить действие в самом редукторе? У меня есть индикатор и аудио элемент. Цель состоит в том, чтобы обновить индикатор выполнения, когда время обновляется в аудиоэлементе. Но я не знаю, где разместить обработчик событий 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 (без реакции), содержащий аудиообъект. Если бы это не было в состоянии, как бы я управлял воспроизведением / остановкой, пропуском и т. Д.?
Кланм

2
Возможно, вы захотите взглянуть на
саксофон

Ответы:


153

Распределение действия внутри редуктора является анти-паттерном . Ваш редуктор должен быть без побочных эффектов, просто переваривая полезную нагрузку действия и возвращая новый объект состояния. Добавление слушателей и диспетчеризация действий внутри редуктора могут привести к цепным действиям и другим побочным эффектам.

Похоже, ваш инициализированный 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поэтому вам не нужно вызывать диспетчер напрямую.


Большое спасибо за разъяснения! Но я до сих пор не знаю, как получить доступ к диспетчеру. Я всегда использовал метод подключения от Reaction-Redux. но я не знаю, как вызвать его в методе updateProgress. Или есть другой способ получить диспетчер. может с реквизитом? спасибо
klanm

Нет проблем. Вы можете передать действие MyAudioPlayerкомпоненту из родительского контейнера, который connectредактируется react-redux. Проверьте, как сделать это с mapDispatchToPropsздесь: github.com/reactjs/react-redux/blob/master/docs/...
ebuat3989

6
Где символ updateProgressActionопределяется в вашем примере?
Чарльз Пракаш Дасари

2
Если вы не должны отправлять действие внутри редуктора, то нарушает ли правила редукса редукс?
Эрик Винер

2
@EricWiener Я считаю, redux-thunkчто отправляет действие из другого действия, а не редуктора. stackoverflow.com/questions/35411423/…
sallf

162

Запуск другой диспетчеризации до того, как ваш редуктор закончен, является анти-паттерном , потому что состояние, которое вы получили в начале вашего редуктора, больше не будет текущим состоянием приложения после его завершения. Но планирование другой отправки изнутри редуктора НЕ является анти-паттерном . Фактически это то, что делает язык 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
Марсело, ваш пост в блоге отлично справляется с описанием обстоятельств вашего подхода, поэтому я ссылаюсь
Дежей Клейтон,

3
Это было именно то, что мне было нужно, за исключением промежуточных программных разрывов, dispatchкоторые должны возвращать действие. Я изменил последние строки на: const res = next(actionWithAsyncDispatch); syncActivityFinished = true; flushQueue(); return res;и это сработало отлично.
zanerock

1
Если вы не должны отправлять действие внутри редуктора, то нарушает ли правила редукса редукс?
Эрик Винер

Как это работает, когда вы пытаетесь обработать ответы веб-сокета? Это мой экспорт по умолчанию (состояние, действие) => {const m2 = [... state.messages, action.payload] return Object.assign ({}, состояние, {messages: m2,})} и я все еще получить эту ошибку «В промежутках между отправками была обнаружена мутация состояния»
Quantumpotato

12

Вы можете попробовать использовать такую ​​библиотеку, как redux-saga . Это позволяет очень аккуратно упорядочить асинхронные функции, запустить действия, использовать задержки и многое другое. Это очень мощно!


1
Можете ли вы указать, как добиться «планирования другой отправки внутри редуктора» с помощью redux-saga?
XPD

1
@XPD, можешь ли ты объяснить немного больше, чего ты хочешь достичь? Если вы пытаетесь использовать действие-редуктор для отправки другого действия, вы не сможете обойтись без чего-то похожего на redux-saga.
chandlervdw

1
Например, предположим, что у вас есть магазин предметов, в который вы загрузили часть предметов. Товары загружаются лениво. Предположим, у товара есть поставщик. Поставщики тоже лениво грузятся. Так что в этом случае может быть элемент, который не был загружен его поставщиком. В редукторе элементов, если нам нужно получить информацию о элементе, который еще не был загружен, мы должны снова загрузить данные с сервера через редуктор. В таком случае, как redux-saga помогает внутри редуктора?
XPD

1
Хорошо, допустим, вы хотели отключить этот запрос supplierинформации, когда пользователь пытается посетить itemстраницу с подробностями. Вы componentDidMount()бы выпалить функцию , которая отправляет действие, скажем FETCH_SUPPLIER. Внутри редуктора вы можете отметить что-то вроде a, loading: trueчтобы показать счетчик во время выполнения запроса. redux-sagaбудет прислушиваться к этому действию и в ответ запустить фактический запрос. Используя функции генератора, он может дождаться ответа и сбросить его FETCH_SUPPLIER_SUCCEEDED. Затем редуктор обновляет магазин информацией о поставщике.
chandlervdw

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.