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

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

РЕДАКТИРОВАТЬ: для ясности - этот компонент вызывает 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, но это выглядит хакерским. Это правильный подход или я что-то упускаю?


person Ed_    schedule 25.01.2019    source источник
comment
Непонятно, для чего нужен этот интервал. Поскольку timePoint не изменяется с течением времени, он повторно отображает компонент с теми же данными. Пожалуйста, опишите, каково ожидаемое поведение компонента. Предпочтительный способ зависит от этого.   -  person Estus Flask    schedule 25.01.2019
comment
@estus - извините, я не включил это, потому что это второстепенная информация, которая на самом деле не нужна. Я добавил, почему мне нужно обновлять данные при редактировании, см. Выше.   -  person Ed_    schedule 25.01.2019


Ответы (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 после внесения изменений в существующее состояние, но если существующее состояние равно null и ничего не передается в сеттер, это будет случай, когда React мог знать, что состояние не изменилось с момента последнего рендеринга, и оптимизировать его. Вместо того, чтобы заставлять кого-то копаться в точном поведении React или беспокоиться о том, может ли это поведение измениться в будущем, я бы фактически изменил состояние.
person Ryan Cogswell    schedule 25.01.2019
comment
Спасибо, Райан, интересное дополнение - я скоро приму этот ответ, если не встречу никакого сопротивления подходу. - person Ed_; 25.01.2019