React + Redux, как визуализировать не после каждой отправки, а после нескольких?

Я пытаюсь сделать несколько изменений в хранилище, но не отрисовывать до тех пор, пока все изменения не будут выполнены. Я хотел сделать это с помощью decux-thunk.

Вот мой создатель действия:

function addProp(name, value) {
    return { type:'ADD_PROP', name, value }
}

function multiGeoChanges(...changes) {
    // my goal here is to make multiple changes to geo, and make sure that react doesnt update the render till the end
    return async function(dispatch, getState) {
        for (let change of changes) {
            dispatch(change);
            await promiseTimeout(2000);
        }
    }
}

Я отправляю своего создателя действия async следующим образом:

store.dispatch(multiGeoChanges(addProp(1, "val1"), addProp(2, "val2"), addProp(3, "val3")));

Однако это вызывает реакцию на рендеринг после каждого dispatch. Я новичок в redux-thunk, я никогда не использовал асинхронное промежуточное ПО, но я думал, что это может помочь мне здесь.

Ответ 1

Есть способы достижения цели:

Классический способ:

обычно: Действия описывают тот факт, что что-то произошло, но не указывают, как изменяется состояние приложения в ответ. Это работа редукторов. Это также означает, что действия не являются сеттерами.

Таким образом, вы можете описать то, что произошло, и накопить изменения, и отправить одно действие примерно так:

const multipleAddProp = (changedProps) =>({
   type:'MULTIPLE_ADD_PROP', changedProps
});

А затем отреагируйте на действие в редукторе:

const geo=(state,action)=>{
   ...
   switch (action.type){
   case 'MULTIPLE_ADD_PROP':
     // apply new props
   ...
   }
}

Другой способ Когда перерисовка имеет решающее значение:

тогда вы можете рассмотреть возможность ограничения компонентов, которые могут быть переопределены при изменении состояния. Например, вы можете использовать shouldComponentUpdate чтобы проверить, должен ли компонент отображаться или нет. Также вы можете использовать перевыбор, чтобы не перерисовывать подключенные компоненты после вычисления производных данных...


Нестандартный способ: избыточно-пакетное действие

Это работает что-то вроде транзакции.

В этом примере подписчики будут уведомлены один раз:

import { batchActions } from 'redux-batched-actions';

const multiGeoChanges=(...arrayOfActions)=> dispatch => {
    dispatch( batchActions(arrayOfActions) );
}

Ответ 2

@Коковин Владислав отвечает правильно. Чтобы добавить дополнительный контекст:

Redux будет уведомлять всех подписчиков после каждой отправки. Сократить повторные рендеры, либо отправить меньше раз, либо использовать один из нескольких подходов для "пакетных" рассылок и уведомлений. Для получения дополнительной информации см. FAQ по Redux по событиям обновления: http://redux.js.org/docs/faq/Performance.html#performance-update-events.

Я также недавно написал пару сообщений в блоге, которые относятся к этой теме. Idiomatic Redux: Мысли о Thunks, Sagas, Abstraction и Reusability обсуждают плюсы и минусы использования thunks и суммируют несколько способов обработки пакетных рассылок, Практическая Redux Часть 6: Связанные списки, формы и производительность описывает несколько ключевых аспектов, которые необходимо знать о производительности Redux.

Наконец, есть еще несколько библиотек, которые могут помочь в доработке уведомлений об изменениях в магазине. См. Раздел Store # Store Change Subscriptions в разделе Каталог дополнений Redux для списка соответствующих дополнений. В частности, вас может заинтересовать https://github.com/manaflair/redux-batch, который позволит вам отправлять массив действий только с одним уведомлением.

Ответ 3

По дизайну, когда состояние, удерживаемое хранилищем, изменяет представление, которое должно отображать.

Вы можете избежать этого, обновив состояние один раз.

Если вы используете promises, вы можете использовать Promise.all, чтобы дождаться разрешения всех promises, а затем отправить новое действие в хранилище с вычисленным результатом. https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise/all

Что-то вроде этого:

Promise.all([p1, p2, p3, p4, p5]).then(changes => { 
  dispatch(changes)
}, err => {
  // deal with error
});

Конечно, вам понадобится действие, которое будет касаться множества реквизитов, что-то вроде addManyProps, это должно обновить состояние один раз, в результате получится один рендер.

Ответ 4

сокращения-пакетные действия Создатель пакетного действия и связанный с ним редуктор более высокого порядка для редукции, который позволяет регистрировать уведомления абонентов для массива действий.

Ответ 5

Немного поздно, но я думаю, что это гораздо более meta.batch решение, которое позволяет вам добавлять meta.batch к действиям, которые вы хотели бы объединить, в одно react обновление.

import raf from 'raf'
import { batchedSubscribe } from 'redux-batched-subscribe'

let notify = null
let rafId = null

const shouldBatch = action => action?.meta?.batch

export const batchedSubscribeEnhancer = batchedSubscribe(freshNotify => (notify = freshNotify))

export const batchedSubscribeMiddleware = () => next => action => {
  const resolved = next(action)

  if (notify && rafId === null && !shouldBatch(action)) {
    notify()
  } else if (!rafId) {
    rafId = raf(() => {
      rafId = null
      notify()
    })
  }

  return resolved
}

Затем подключитесь к вашему магазину

mport { applyMiddleware, compose, createStore } from 'redux'
import { batchedSubscribeMiddleware, batchedSubscribeEnhancer } from './batching'

const store = createStore(
  reducer,
  intialState,
  compose(
    batchedSubscribeEnhancer,
    applyMiddleware(batchedSubscribeMiddleware)
  )
)

Ответ 6

Немного поздно, но я думаю, что это гораздо более meta.batch решение, которое позволяет вам добавлять meta.batch к действиям, которые вы хотели бы объединить, в одно react обновление. В качестве бонуса этот подход работает с асинхронными действиями.

import raf from 'raf'
import { batchedSubscribe } from 'redux-batched-subscribe'

let notify = null
let rafId = null

const shouldBatch = action => action?.meta?.batch

export const batchedSubscribeEnhancer = batchedSubscribe(freshNotify => (notify = freshNotify))

export const batchedSubscribeMiddleware = () => next => action => {
  const resolved = next(action)

  if (notify && rafId === null && !shouldBatch(action)) {
    notify()
  } else if (!rafId) {
    rafId = raf(() => {
      rafId = null
      notify()
    })
  }

  return resolved
}

Затем подключитесь к вашему магазину

mport { applyMiddleware, compose, createStore } from 'redux'
import { batchedSubscribeMiddleware, batchedSubscribeEnhancer } from './batching'

const store = createStore(
  reducer,
  intialState,
  compose(
    batchedSubscribeEnhancer,
    applyMiddleware(batchedSubscribeMiddleware)
  )
)

Ответ 7

В реаги-редуксе 7+ дозирование теперь встроено:

function myThunk() {

   return (dispatch, getState) => {

       // should only result in one combined re-render, not two

       batch(() => {

           dispatch(increment());

           dispatch(increment());

       })

   }

}