'proper' 在 hooks 间隔后更新反应组件的方法是什么?

What is the 'proper' way to update a react component after an interval with hooks?

我正在使用支持挂钩的 React 的 alpha 版本,并且想要验证我在 间隔 之后更新组件中的文本的方法,而不是 渲染组件的次数超过当道具改变时需要。

EDIT: For clarity - this component is calling moment(timepoint).fromNow() within the formatTimeString function (docs here), so the update isn't totally unneccessary, I promise!

我以前有:

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>
  );
};

这个 "works" - 如果 props 改变,组件会正确更新,并且组件会在每个时间间隔更新,但是 在安装时,当 prop 改变时,组件将渲染两次。

这是因为 useEffect 之后 运行 timePoint 的值更改时产生的渲染,并且在我的 useEffect 回调中我我立即调用 setState 方法触发额外的渲染。

显然,如果我删除对 setText 的调用,组件似乎不会在 prop 更改时发生更改(直到间隔运行),因为 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 道具改变时渲染一次,但感觉很糟糕。这是处理事情的正确方法,还是我遗漏了什么?

我认为这种方法似乎不错。我要做的主要改变是每次都实际更改值,因此它是:

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>
  );
};

我建议进行此更改有两个原因:

  • 我认为这有助于调试 and/or 验证正在发生的确切行为。然后,您可以在开发工具中查看此状态,并准确查看以这种方式触发重新渲染的次数。
  • 另一个原因只是为了让查看此代码的人更有信心,相信它确实会按预期执行。尽管 setState 可靠地触发了重新渲染(并且 React 不太可能更改它,因为它会破坏太多),看到这段代码的人想知道 "Does React guarantee a re-render if a setState call doesn't result in any change to the state?" 的主要原因 setState 总是触发重新渲染,即使未更改是因为在对现有状态进行突变后调用 setState 的可能性,但如果现有状态为空并且没有任何内容传递给 setter,在这种情况下,React 可以 知道自上次渲染以来状态没有改变并针对它进行优化。与其强迫某人深入研究 React 的确切行为或担心该行为将来是否会发生变化,不如对状态进行实际更改。