如何在反应功能组件中实现全局变量

How to implement global variable in react functional components

我是新手,决定通过使用 class 和功能组件实现一个简单的秒表来练习。

我使用 class 组件成功实现了秒表。下面是代码:

Class 组件


class Stopwatch extends Component {
  state = {
    status: false,
    ms: 0,
    seconds: 0,
    minutes: 0,
  };

  stopms;
  stopSeconds;
  stopMinutes;

  handleClick = () => {
    this.changeStatus();
    if (this.state.status) {
      clearInterval(this.stopms);
      clearInterval(this.stopSeconds);
      clearInterval(this.stopMinutes);
    } else {
      this.stopms = setInterval(this.changeMs, 1);
      this.stopSeconds = setInterval(this.changeSeconds, 1000);
      this.stopMinutes = setInterval(this.changeMinutes, 60000);
    }
  };

  changeStatus = () => {
    return this.setState((state) => {
      return { status: !state.status };
    });
  };

  changeMs = () => {
    return this.setState((state) => {
      if (state.ms === 99) {
        return { ms: 0 };
      } else {
        return { ms: state.ms + 1 };
      }
    });
  };

  changeSeconds = () => {
    return this.setState((state) => {
      if (state.seconds === 59) {
        return { seconds: 0 };
      } else {
        return { seconds: state.seconds + 1 };
      }
    });
  };

  changeMinutes = () => {
    return this.setState((state) => {
      if (state.seconds === 59) {
        return { minutes: 0 };
      } else {
        return { minutes: state.minutes + 1 };
      }
    });
  };

  handleReset = () => {
    clearInterval(this.stopms);
    clearInterval(this.stopSeconds);
    clearInterval(this.stopMinutes);
    this.setState({ seconds: 0, status: false, minutes: 0, ms: 0 });
  };

  componentWillUnmount() {
    clearInterval(this.stopms);
    clearInterval(this.stopSeconds);
    clearInterval(this.stopMinutes);
  }

  render() {
    return (
      <div>
        <h1>
          {this.state.minutes} : {this.state.seconds} .{" "}
          <span>{this.state.ms}</span>
        </h1>
        <button className="btn btn-lg btn-dark" onClick={this.handleClick}>
          {this.state.status === false ? "Start" : "Pause"}
        </button>
        <button className="btn btn-lg btn-dark" onClick={this.handleReset}>
          Reset
        </button>
      </div>
    );
  }
}

export default Stopwatch;

现在我尝试实现与上面相同的代码,但使用如下所示的功能组件:

功能组件


function Stopwatch() {
  const [timeState, setTimeState] = useState({
    status: false,
    ms: 0,
    seconds: 0,
    minutes: 0,
  });

  let stopms;
  let stopSeconds;
  let stopMinutes;

  const handleClick = () => {
    changeStatus();
    if (timeState.status) {
      clearInterval(stopms);
      clearInterval(stopSeconds);
      clearInterval(stopMinutes);
    } else {
      stopms = setInterval(changeMs, 1);
      stopSeconds = setInterval(changeSeconds, 1000);
      stopMinutes = setInterval(changeMinutes, 60000);
    }
  };

  const changeStatus = () => {
    return setTimeState((prevState) => {
      return { ...prevState, status: !prevState.status };
    });
  };

  const changeMs = () => {
    return setTimeState((prevState) => {
      if (prevState.ms === 99) {
        return { ...prevState, ms: 0 };
      } else {
        return { ...prevState, ms: prevState.ms + 1 };
      }
    });
  };

  const changeSeconds = () => {
    return setTimeState((prevState) => {
      if (prevState.seconds === 59) {
        return { ...prevState, seconds: 0 };
      } else {
        return { ...prevState, seconds: prevState.seconds + 1 };
      }
    });
  };

  const changeMinutes = () => {
    return setTimeState((prevState) => {
      if (prevState.seconds === 59) {
        return { ...prevState, minutes: 0 };
      } else {
        return { ...prevState, minutes: prevState.minutes + 1 };
      }
    });
  };

  const handleReset = () => {
    clearInterval(stopms);
    clearInterval(stopSeconds);
    clearInterval(stopMinutes);
    setTimeState({ seconds: 0, status: false, minutes: 0, ms: 0 });
  };

  return (
    <div>
      <h1>
        {timeState.minutes} : {timeState.seconds} . <span>{timeState.ms}</span>
      </h1>
      <button className="btn btn-lg btn-dark" onClick={handleClick}>
        {timeState.status === false ? "Start" : "Stop"}
      </button>
      <button className="btn btn-lg btn-dark" onClick={handleReset}>
        Reset
      </button>
    </div>
  );
}

export default Stopwatch;

问题

在 class 组件中,我使用 handleClick 函数实现了“暂停”功能,该函数调用 clearInterval 并将其参数作为我最初声明的全局变量 stopms、stopSeconds、stopMinutes。这工作得很好,因为当秒表开始计时时,这些全局变量保存了来自各自 setInterval 的值return。

现在在功能组件中,我通过使用“let”关键字声明相同的全局变量来复制相同的逻辑。但是“暂停”功能不起作用。当点击“开始”按钮并调用 handleClick 函数时,将调用 setIntervals 并将它们的 return 值存储在各自的全局变量中。但是当按下“暂停”按钮时,所有全局变量的值都为“未定义”。

我想知道是否有任何其他方法可以声明全局变量并使用它们在组件的整个生命周期中使用状态来保存值。

只要状态发生变化,功能组件就会从上到下执行,所以整个功能是 re-executed 这就是 returns 新 JSX 的方式,将其与 class 组件进行比较只有 render() 功能在渲染上执行,这就是功能组件的工作方式。

问题是您的全局变量实际上不是全局变量而是函数的一部分,因此每次渲染时它们都会重新初始化。

解决这个问题的两种方法

将您的变量移动到状态

function Stopwatch() {
  const [timeState, setTimeState] = useState({
    status: false,
    ms: 0,
    seconds: 0,
    minutes: 0,
    stopms : null,
    stopSeconds : null,
    stopMinutes: null,
  });

  const handleClick = () => {
    changeStatus();
    if (timeState.status) {
      clearInterval(timeState.stopms);
      clearInterval(timeState.stopSeconds);
      clearInterval(timeState.stopMinutes);
    } else {
      let stopms = setInterval(changeMs, 1);
      let stopSeconds = setInterval(changeSeconds, 1000);
      let stopMinutes = setInterval(changeMinutes, 60000);

      setTimeState(prev => ({..prev, stopms, stopSeconds, stopMinutes})); // update the values in state
    }
  };

   ...... 

   const handleReset = () => {
    clearInterval(timeState.stopms); // use the same values to clear them
    clearInterval(timeState.stopSeconds);
    clearInterval(timeState.stopMinutes);
    .....
  };

 ..... 

 } 

或者通过将它们放在您的组件之外使它们成为全局的,可以但不推荐。

在你的组件文件中。

 // declare them just above your function
 let stopms;
 let stopSeconds;
 let stopMinutes;
  
 function Stopwatch() {
  const [timeState, setTimeState] = useState({
    status: false,
    ms: 0,
    seconds: 0,
    minutes: 0,
  });
  .....

  const handleClick = () => {
    changeStatus();
    if (timeState.status) {
      clearInterval(stopms);
      clearInterval(stopSeconds);
      clearInterval(stopMinutes);
    } else {
      stopms = setInterval(changeMs, 1);
      stopSeconds = setInterval(changeSeconds, 1000);
      stopMinutes = setInterval(changeMinutes, 60000);
    }
   .......
  };