带有钩子的 React 函数组件计数器

React Function Component counter with hooks

我正在尝试了解新的 React 挂钩及其用例。

我的目标是单个组件计数,并且每个 x 刻度计数另一个计数器。

我是用useEffect和useState实现的,主要有两个问题:
1. 在调用超时之前卸载组件时发生内存泄漏(使用 react-router 导航时)
2. 由于useEffect 和useState 都会触发渲染,组件在每个tick 渲染两次。

我认为解决方案是使用 useRef 或 useMemo,但我还没有弄清楚。

我当前的组件(带打字稿):

import React from "react";

const Component: React.FC = () => {
  const [trigger, setTrigger] = React.useState(0);
  const [timer, setTimer] = React.useState({ cycle: 0, count: 0 });

  let refTimer = React.useRef({ cycle: 0, count: 0 });

  // useRef
  // React.useEffect(() => {
  //   setInterval(() => {
  //     console.log("tick");
  //     if (refTimer.current.count % 2 === 0) {
  //       refTimer.current.cycle++;
  //       setTimer(refTimer.current);
  //     }
  //     refTimer.current.count++;
  //     setTimer(refTimer.current);
  //     // console.log(timer);
  //   }, 1000);
  // }, []);

  // useState
  React.useEffect(() => {
    console.log("effect tick");
    setTimeout(() => {
      console.log("tick");
      const count = timer.count + 1;
      if (count % 2 === 0) {
        const cycle = timer.cycle + 1;
        setTimer({ ...timer, count, cycle });
        return;
      }
      setTimer({ ...timer, count });
    }, 1000);
  }, [timer]);
  return (
    <div>
      <br />
      <br />
      <br />
      <br /> Playground:
      <div>Count: {timer.count}</div>
      <div>Cycle: {timer.cycle}</div>
      <button type="button" onClick={(): void => setTrigger(trigger + 1)}>
        Trigger Count: {trigger}
      </button>
    </div>
  );
};

export default Component;

正如我所说,像这样我有提到的两个问题。我可以完全删除可以修复双重渲染的 useEffect,但是当我单击触发按钮时,滴答声会堆积起来,这比双重渲染更糟糕。

评论的 useRef 部分是我尝试过的,但不知何故不起作用。

感谢大家的帮助!

编辑: 第三个小问题是,像这样,计数器仅使用 setTimeout 运行,这将触发另一个 setTimeout,因此如果该过程需要一些时间,它就不会真正是一个精确的间隔。

所以我的目标是在一个单独的进程中运行的时间间隔(我会说在 useEffect 中)会导致每次滴答重新渲染并且不会在每次调用或其他东西触发重新渲染时叠加.

您可以修复#1 中提到的内存泄漏。

React.useEffect(() => {
  console.log("effect tick", timer);

  // ..  get the timeout ID to clear on unmount
  const id = setTimeout(() => {
    console.log(`tick id=${id}`, timer);
    const count = timer.count + 1;
    if (count % 2 === 0) {
      const cycle = timer.cycle + 1;
      setTimer({ ...timer, count, cycle });
      return;
    }
    setTimer({ ...timer, count });
  }, 1000);

  // ...  Clean up here with the ID on unmount
  return () => clearTimeout(id);
}, [timer]);

关于#2 双重渲染,你能更具体一点吗? Before/After 在上面 useEffect 中清理,我无法理解你的意思,因为当前的控制台日志似乎按预期工作。