Компонент визуализируется дважды при обновлении контекста асинхронной реакции - PullRequest
3 голосов
/ 04 октября 2019

У меня есть приложение React, которое управляет состоянием, используя контекст React. Я создал простое счетное воспроизведение с приращением .

Существует два контекста. Один для хранения состояния, а второй для отправки. Это шаблон, взятый из этой статьи .

Контекст состояния просто хранит одно число, и в этом состоянии можно вызвать только одно действие:

function reducer(state: State, action: Action): State {
  switch (action.type) {
    case "inc": {
      return state + 1;
    }
  }
}

У меня также есть две вспомогательные функции для синхронного и асинхронного увеличения значения:

async function asyncInc(dispatch: React.Dispatch<Action>) {
  await delay(1000);
  dispatch({ type: "inc" });
  dispatch({ type: "inc" });
}

function syncInc(dispatch: React.Dispatch<Action>) {
  dispatch({ type: "inc" });
  dispatch({ type: "inc" });
}

А вот как вы используете его в компоненте:

const counter = useCounterState();
const dispatch = useCounterDispatch();

return (
  <React.Fragment>
    <button onClick={() => asyncInc(dispatch)}>async increment</button>
    <button onClick={() => syncInc(dispatch)}>sync increment</button>
    <div>{counter}</div>
  </React.Fragment>
);

Теперь, когда я нажимаю sync increment кнопка все будет работать как положено. Он будет дважды вызывать операцию inc, увеличивая счетчик на 2 и выполняя только одну повторную визуализацию компонента.

Когда я нажимаю кнопку async increment, он сначала ждет одну секунду и выполняет операцию incдважды, но он будет перерисовывать компонент дважды.

Вы должны открыть консоль, чтобы увидеть журналы от компонентов рендеринга.

Я вроде понимаю, почему это так. Когда это синхронная операция, все происходит во время рендеринга компонента, поэтому он сначала манипулирует состоянием и рендерит в конце. Когда это асинхронная операция, она сначала выполняет рендеринг компонента, а через одну секунду она обновляет состояние после запуска повторного рендеринга и обновляет во второй раз, вызывая следующий повторный рендеринг.

Таким образом, можно выполнить асинхронное обновление состояния, выполнивтолько один повтор? В том же воспроизведении есть похожий пример, но с использованием React.useState, у которого тоже есть та же проблема.

Может, мы как-то будем пакетно обновлять? Или мне нужно создать другое действие, которое бы одновременно выполняло несколько операций над состоянием?

Я также создал это воспроизведение , которое решает эту проблему, выполняя множество действий, но яЛюбопытно, можно ли этого избежать.

Ответы [ 2 ]

2 голосов
/ 04 октября 2019

По сути, для syncInc() вы видите пакетирование для событий щелчка. Таким образом, вы видите, что он рендерит только один раз.

React упаковывает все setStates, выполненные во время обработчика события React , и применяет их непосредственно перед выходом из собственного обработчика события браузера.

Для вашего asyncInc() это не входит в сферу действия обработчика событий (из-за async), поэтому ожидается, что вы получите два повторных рендеринга (т.е. не пакетные обновления состояния).

Может, мы как-то пакетные обновления?

Да Реагировать может пакетные обновления в рамках асинхронной функции.

1 голос
/ 04 октября 2019

Если это не вызывает проблем с производительностью, я бы рекомендовал не беспокоиться о дополнительных рендерах. Я нашел этот пост о исправлении медленных рендеров, прежде чем беспокоиться о повторных рендерах , который может быть полезен в этой теме.

Однако, похоже, что в React unstable (поэтому его не следует использовать) API для пакетного обновления ReactDOM.unstable_batchedUpdates. Однако использование этого может вызвать головную боль в будущем, если она будет удалена или изменена. Но чтобы ответить на ваш вопрос «можем ли мы каким-то образом обновить пакет?» , да.

const asyncInc = async () => {
  await delay(1000);

  ReactDOM.unstable_batchedUpdates(() => {
    setCounter(counter + 1);
    setCounter(counter + 1);
  });
};
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...