You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

React秒表本地存储读取问题及代码优化求助

Hey there! Let's tackle your timer issues step by step. I noticed a few key problems that are preventing your localStorage persistence from working, plus some easy optimizations to make the code cleaner and more reliable.

Key Issues in Your Current Code

  • Incorrect localStorage checks and state assignments: In componentDidMount, your condition !localStorage.getItem("sec") === null is backwards (it should check if the item exists, not if the negation equals null). Also, you accidentally set seconds instead of minutes and hours in two of those setState calls—oops!
  • String vs. Number type mismatch: localStorage stores everything as strings, so when you retrieve values, you need to convert them to numbers. Otherwise, adding 1 to a string like "5" will give you "51" instead of 6.
  • Flawed timing logic: When seconds hit 60, you're setting seconds to localStorage.getItem("sec") instead of resetting to 0. Same issue with minutes hitting 60. Also, using this.state directly in setInterval can lead to stale state because setState is asynchronous.
  • Reset doesn't clear localStorage: Your reset button resets the state but leaves the stored values in localStorage, so the timer will pick up the old values next time it mounts.

Fixed Class Component Code

First, here's a corrected version of your class component that fixes all the above issues:

import React, { Component } from "react";

export default class Timer extends Component {
  constructor(props) {
    super(props);
    // Initialize state with localStorage values (converted to numbers)
    this.state = {
      timerStarted: false,
      timerStopped: true,
      hours: Number(localStorage.getItem("hour")) || 0,
      minutes: Number(localStorage.getItem("min")) || 0,
      seconds: Number(localStorage.getItem("sec")) || 0,
    };
    this.timer = null;
  }

  handleTimerStart = (e) => {
    e.preventDefault();
    if (this.state.timerStopped) {
      this.setState({ timerStarted: true, timerStopped: false });
      this.timer = setInterval(() => {
        this.setState((prevState) => {
          let { hours, minutes, seconds } = prevState;
          
          seconds += 1;
          // Handle seconds to minutes conversion
          if (seconds >= 60) {
            seconds = 0;
            minutes += 1;
            // Handle minutes to hours conversion
            if (minutes >= 60) {
              minutes = 0;
              hours += 1;
            }
          }
          
          return { hours, minutes, seconds };
        });
      }, 1000);
    }
  };

  handleTimerStop = () => {
    this.setState({ timerStarted: false, timerStopped: true });
    clearInterval(this.timer);
    // Save current state to localStorage
    localStorage.setItem("sec", this.state.seconds);
    localStorage.setItem("min", this.state.minutes);
    localStorage.setItem("hour", this.state.hours);
  };

  handelResetTimer = () => {
    this.setState({
      timerStarted: false,
      timerStopped: true,
      hours: 0,
      minutes: 0,
      seconds: 0,
    });
    // Clear localStorage on reset
    localStorage.removeItem("sec");
    localStorage.removeItem("min");
    localStorage.removeItem("hour");
  };

  // Clean up timer on component unmount to prevent memory leaks
  componentWillUnmount() {
    if (this.timer) {
      clearInterval(this.timer);
    }
  }

  render() {
    // Format numbers to show two digits (e.g., 5 → "05")
    const formatTime = (num) => num.toString().padStart(2, "0");
    
    return (
      <div className="container">
        <h2 className="text-center"> React Based Timer </h2>
        <div className="timer-container">
          <div className="current-timer">
            {formatTime(this.state.hours)}:{formatTime(this.state.minutes)}:{formatTime(this.state.seconds)}
          </div>
          <div className="timer-controls">
            <button className="btn btn-success" onClick={this.handleTimerStart}> Start Timer </button>
            <button className="btn btn-alert" onClick={this.handleTimerStop}> Stop Timer </button>
            <button className="btn btn-info"> Capture Time </button>
            <button className="btn btn-danger" onClick={this.handelResetTimer}> Reset! </button>
          </div>
        </div>
      </div>
    );
  }
}

Additional Optimizations (Using React Hooks)

Class components work, but using hooks makes the code more concise and modern. Here's a refactored version that fixes timer drift (a common issue with setInterval) and simplifies state management:

import React, { useState, useEffect, useRef } from "react";

const Timer = () => {
  // Initialize state with localStorage values
  const [hours, setHours] = useState(() => Number(localStorage.getItem("hour")) || 0);
  const [minutes, setMinutes] = useState(() => Number(localStorage.getItem("min")) || 0);
  const [seconds, setSeconds] = useState(() => Number(localStorage.getItem("sec")) || 0);
  const [isRunning, setIsRunning] = useState(false);
  const timerRef = useRef(null);

  // Handle timer logic with timestamps to avoid drift
  useEffect(() => {
    if (isRunning) {
      const startTime = Date.now() - (hours * 3600 + minutes * 60 + seconds) * 1000;
      timerRef.current = setInterval(() => {
        const elapsed = Math.floor((Date.now() - startTime) / 1000);
        setHours(Math.floor(elapsed / 3600));
        setMinutes(Math.floor((elapsed % 3600) / 60));
        setSeconds(elapsed % 60);
      }, 1000);
    } else {
      clearInterval(timerRef.current);
      // Save to localStorage when stopped
      localStorage.setItem("hour", hours);
      localStorage.setItem("min", minutes);
      localStorage.setItem("sec", seconds);
    }

    // Cleanup timer on unmount or when isRunning changes
    return () => clearInterval(timerRef.current);
  }, [isRunning, hours, minutes, seconds]);

  const handleStart = () => setIsRunning(true);
  const handleStop = () => setIsRunning(false);
  const handleReset = () => {
    setIsRunning(false);
    setHours(0);
    setMinutes(0);
    setSeconds(0);
    localStorage.removeItem("hour");
    localStorage.removeItem("min");
    localStorage.removeItem("sec");
  };

  const formatTime = (num) => num.toString().padStart(2, "0");

  return (
    <div className="container">
      <h2 className="text-center"> React Based Timer </h2>
      <div className="timer-container">
        <div className="current-timer">
          {formatTime(hours)}:{formatTime(minutes)}:{formatTime(seconds)}
        </div>
        <div className="timer-controls">
          <button className="btn btn-success" onClick={handleStart} disabled={isRunning}> Start Timer </button>
          <button className="btn btn-alert" onClick={handleStop} disabled={!isRunning}> Stop Timer </button>
          <button className="btn btn-info"> Capture Time </button>
          <button className="btn btn-danger" onClick={handleReset}> Reset! </button>
        </div>
      </div>
    </div>
  );
};

export default Timer;

What's Better About the Hooks Version?

  • No stale state: Uses useRef for the timer and calculates elapsed time with timestamps to avoid issues with asynchronous state updates.
  • Timer drift prevention: Instead of adding 1 every second (which can drift if setInterval is delayed), it calculates time based on actual current time vs. the start time.
  • Cleaner cleanup: Automatically clears the timer when the component unmounts or when the running state changes.
  • Disabled buttons: Prevents starting the timer multiple times or stopping it when it's not running.

内容的提问来源于stack exchange,提问作者Exc

火山引擎 最新活动