Как обрабатывать обратные вызовы сокетов, которые отправляют в redux слишком много и блокируют приложение? react-redux batch?

#reactjs #redux #react-redux

#reactjs #redux #react-redux

Вопрос:

В нашем приложении некоторые обновления данных поступают через сокеты. В обратных вызовах сокета отправляется действие redux. Вот пример

 const onReceiveLocationMessages = function(message) {
      let payload = JSON.parse(message.body);
      console.log("received locations", payload);
      dispatch(onLocationChange(payload));
    };
  

Теперь иногда через сокет поступает сразу много обновлений, поэтому обратный вызов выполняется последовательно, поэтому также запускается много отправлений. Это приводит к отставанию приложения, например, при навигации, открытии меню, нажатии кнопок и т. Д., Потому что, как я предполагаю, происходит много рендеринга.

Я думаю, что-то похожее на batch https://react-redux.js.org/api/batch из react-redux здесь помогло бы. Но, как показывает пример, он объединяет сразу несколько отправлений, и в данном случае это быстрые последовательные отправки.

Итак, какова наилучшая практика для обработки подобных ситуаций?

Комментарии:

1. Похоже, вам нужен какой-то буфер. здесь есть много потенциальных решений, может быть, взгляните на это github.com/rt2zz/redux-action-buffer если бы вы использовали что-то вроде redux-observable, вы могли бы использовать встроенные операторы, которые очень хорошо обрабатывают этот тип буферизации redux-observable.js.org

Ответ №1:

Вы уверены, что не выполняете ненужный рендеринг многих частей вашего приложения или не используете долго работающий редуктор, который вызывает задержку? Если вы используете чистые компоненты с reselect, вы можете предотвратить ненужный рендеринг компонентов и получить менее медленное приложение.

Ниже приведен пример вспомогательной функции с именем createBatchAction , которая выполняет пакетные обновления. Вы передаете создателю действия (toggleColorBatched) помощнику и batchPeriod параметр и возвращаете новый блок создания действия, который будет выполнять все собранные действия в течение периода пакета.

Существует еще один создатель действия с именем toggleColorNotBatched, который отправляется вместе с batchedToggleColorBatche (новое действие thunk, созданное с помощью createBatchAction), но вы можете видеть, что пакетная версия обновляет DOM пакетно.

 const {
  Provider,
  useDispatch,
  useSelector,
  batch,
} = ReactRedux;
const { createStore, applyMiddleware, compose } = Redux;
// helper to batch action
const createBatchAction = (actionCreator, batchPeriod) => {
  const data = { dispatch: null, actions: [] };
  setInterval(() => {
    batch(() =>
      data.actions.forEach((action) => data.dispatch(action))
    );
    data.actions = [];
  }, batchPeriod);
  return (...args) => (dispatch) => {
    data.dispatch = dispatch;
    data.actions.push(actionCreator(...args));
  };
};

const ROWS = 10;
const COLS = 20;
const BLUE = 'blue';
const RED = 'red';
const initial = Array(ROWS)
  .fill()
  .map(() =>
    Array(COLS)
      .fill()
      .map(() => ({ color: BLUE }))
  );
const initialState = {
  batched: initial,
  notBatched: initial,
};
const nextItem = ((row, col) => () => {
  col  = 1;
  if (col >= COLS) {
    col = 0;
    row  = 1;
  }
  if (row >= ROWS) {
    row = 0;
  }
  return { row, col };
})(0, -1);
//action types
const TOGGLE_COLOR_BATHCED = 'TOGGLE_COLOR_BATHCED';
const TOGGLE_COLOR_NOT_BATCHED = 'TOGGLE_COLOR_NOT_BATCHED';
//action creators
const toggleColorBatched = ({ row, col }) => ({
  type: TOGGLE_COLOR_BATHCED,
  payload: { row, col },
});
const toggleColorNotBatched = ({ row, col }) => ({
  type: TOGGLE_COLOR_NOT_BATCHED,
  payload: { row, col },
});
//batched version of toggleColor that batches all actions every second
const batchedToggleColorBatche = createBatchAction(
  toggleColorBatched,
  1000
);
const setToggle = (rows, row, col) =>
  rows.map((val, i) =>
    i === row
      ? val.map((val, i) =>
          i === col
            ? {
                ...val,
                color: val.color === BLUE ? RED : BLUE,
              }
            : val
        )
      : val
  );
const reducer = (state, { type, payload }) => {
  if (type === TOGGLE_COLOR_BATHCED) {
    const { row, col } = payload;
    return {
      ...state,
      batched: setToggle(state.batched, row, col),
    };
  }
  if (type === TOGGLE_COLOR_NOT_BATCHED) {
    const { row, col } = payload;
    return {
      ...state,
      notBatched: setToggle(state.notBatched, row, col),
    };
  }
  return state;
};
//selectors
const selectBatched = (state) => state.batched;
const selectNotBatched = (state) => state.notBatched;
//creating store with redux dev tools
const composeEnhancers =
  window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
  reducer,
  initialState,
  composeEnhancers(
    applyMiddleware(
      //adding thunk like middleware
      ({ dispatch, getState }) => (next) => (action) =>
        typeof action === 'function'
          ? action(dispatch, getState)
          : next(action)
    )
  )
);
// Col only re renders if props passed to it change
const Col = React.memo(function Col({ col }) {
  return <td style={{ color: col.color }}>X</td>;
});
// Row only re renders if props passed to it change
const Row = React.memo(function Row({ row }) {
  return (
    <tr>
      {row.map((col, i) => (
        <Col key={i} col={col} />
      ))}
    </tr>
  );
});
const App = () => {
  const batched = useSelector(selectBatched);
  const notBatched = useSelector(selectNotBatched);
  const dispatch = useDispatch();
  React.useEffect(() => {
    const interval = setInterval(() => {
      const item = nextItem();
      dispatch(batchedToggleColorBatche(item));
      dispatch(toggleColorNotBatched(item));
    }, 50);
    const item = nextItem();
    dispatch(batchedToggleColorBatche(item));
    dispatch(toggleColorNotBatched(item));
    return () => clearInterval(interval);
  }, [dispatch]);
  return (
    <table>
      <tbody>
        <tr>
          <td>
            <h1>batched</h1>
          </td>
          <td>
            <h1>not batched</h1>
          </td>
        </tr>
        <tr>
          <td>
            <table>
              <tbody>
                {batched.map((row, i) => (
                  <Row key={i} row={row} />
                ))}
              </tbody>
            </table>
          </td>
          <td>
            <table>
              <tbody>
                {notBatched.map((row, i) => (
                  <Row key={i} row={row} />
                ))}
              </tbody>
            </table>
          </td>
        </tr>
      </tbody>
    </table>
  );
};

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);  
 <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/reselect/4.0.0/reselect.min.js"></script>
<div id="root"></div>