Каков "правильный" способ обновления реагирующего компонента после интервала с перехватами?

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

РЕДАКТИРОВАТЬ: Для ясности - этот компонент вызывает moment(timepoint).fromNow() в пределах formatTimeString функция ( документы здесь), так что обновление не является абсолютно ненужным, я обещаю!

У меня ранее было:

const FromNowString = ({ timePoint, ...rest }) => {
  const [text, setText] = useState(formatTimeString(timePoint));

  useEffect(() => {
    setText(formatTimeString(timePoint));
    let updateInterval = setInterval(
      () => setText(formatTimeString(timePoint)),
      30000
    );
    return () => {
      clearInterval(updateInterval);
    };
  }, [timePoint]);

  // Note the console log here is so we can see when renders occur
  return (
    <StyledText tagName="span" {...rest}>
      {console.log('render') || text}
    </StyledText>
  );
};

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

Это потому что useEffect запускается после рендера, который получается, когда значение timePoint изменения и внутри моего useEffect обратный звонок, я сразу звоню setState метод, который запускает дополнительный рендер.

Очевидно, если я уберу этот призыв к setTextкомпонент не изменяется при изменении реквизита (до тех пор, пока не закончится интервал), потому что text все тот же.

Наконец я понял, что могу запустить рендеринг, установив переменную состояния, которая мне на самом деле не нужна, например:

const FromNowString = ({ timePoint, ...rest }) => {
  // We never actually use this state value
  const [, triggerRender] = useState(null);

  useEffect(() => {
    let updateInterval = setInterval(() => triggerRender(), 30000);
    return () => {
      clearInterval(updateInterval);
    };
  }, [timePoint]);

  return (
    <StyledText tagName="span" {...rest}>
      {console.log("render") || formatTimeString(timePoint)}
    </StyledText>
  );
};

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

1 ответ

Решение

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

const FromNowString = ({ timePoint, ...rest }) => {
  const [, triggerRender] = useState(0);

  useEffect(() => {
    const updateInterval = setInterval(() => triggerRender(prevTriggerIndex => prevTriggerIndex + 1), 30000);
    return () => {
      clearInterval(updateInterval);
    };
  }, [timePoint]);

  return (
    <StyledText tagName="span" {...rest}>
      {console.log("render") || formatTimeString(timePoint)}
    </StyledText>
  );
};

У меня есть две причины для предложения этого изменения:

  • Я думаю, что это поможет при отладке и / или проверке точного поведения, которое происходит. Затем вы можете посмотреть на это состояние в инструментах разработчика и увидеть, сколько раз вы запускали повторную визуализацию таким образом.
  • Другая причина - просто дать людям, которые смотрят на этот код, больше уверенности в том, что он действительно будет делать то, для чего предназначен. Даже если setState надежно запускает повторный рендеринг (и React вряд ли изменит это, поскольку он слишком сильно сломается), было бы разумно, чтобы кто-то, взглянув на этот код, задумался: "Гарантирует ли React повторный рендер, если setState вызов не приводит к каким-либо изменениям в состоянии?"Основная причина setState всегда вызывает повторную визуализацию, даже если она не изменена из-за возможности вызова setState после выполнения мутаций в существующем состоянии, но если существующее состояние равно нулю и ничего не передается в установщик, это может быть в случае, когда React может знать, что состояние не изменилось с момента последнего рендеринга, и оптимизировать его. Вместо того, чтобы заставлять кого-то копаться в точном поведении React или беспокоиться о том, может ли это поведение измениться в будущем, я бы сделал реальное изменение состояния.
Другие вопросы по тегам