You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

浏览器LocalStorage计时函数执行removeItem后未删除问题排查

问题分析与解决

核心问题

你遇到的LocalStorage键值无法删除,本质是组件渲染时的重复写入操作覆盖了删除动作:

  • 不管是原代码的if还是else分支,每次组件渲染都会执行window.localStorage.setItem(productId, time),哪怕你在useEffect里删除了键值,组件重新渲染时又会把当前的time值写回LocalStorage,导致删不掉。
  • useEffect的逻辑存在闭包问题,setTime(time - 1000)里的time始终是初始值,计时逻辑会出错;同时过期判断的时机不对,应该在计时更新后检查是否到0。
  • 代码冗余,if和else分支的逻辑几乎完全重复,容易导致维护问题。

修复步骤

  1. 移除渲染阶段的LocalStorage写入:只在初始化和计时更新时同步LocalStorage,避免每次渲染都覆盖。
  2. 修正计时逻辑的闭包问题:使用函数式更新setTime(prev => prev - 1000)获取最新状态值。
  3. 统一逻辑分支:把初始化time的逻辑合并,避免重复代码。
  4. 调整过期判断时机:在计时触发后检查时间是否为0,再执行删除和状态更新操作。
  5. 清理定时器:在组件卸载时清除定时器,避免内存泄漏。

修复后的完整代码

import React, { useState, useEffect } from "react";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import customFetch from "../utility/customFetch";

function TimeFunction({ shelfLife, productId, id, status, expireIn }) {
  const queryClient = useQueryClient();

  const { mutate: updateAddedProduct, isLoading } = useMutation({
    mutationFn: (updatedProduct) => {
      return customFetch.patch(`/updateAddedProductOne`, updatedProduct);
    },
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ["addedProducts"] });
    },
    onError: (error) => {
      console.error(error);
    },
  });

  if (isLoading) {
    return <h3>Loading...</h3>;
  }

  // 统一初始化time:优先从LocalStorage取,没有则用shelfLife计算
  const storedTime = window.localStorage.getItem(productId);
  const initialTime = storedTime ? Number(storedTime) : shelfLife * 60 * 1000;
  const [time, setTime] = useState(initialTime);

  useEffect(() => {
    // 只有当time不为0时才启动定时器
    if (time <= 0) return;

    const timerBoss = setTimeout(() => {
      setTime(prevTime => {
        const newTime = prevTime - 1000;
        // 同步到LocalStorage
        window.localStorage.setItem(productId, newTime);
        
        // 检查是否过期
        if (newTime <= 0) {
          handleProductExpire();
        }
        return newTime;
      });
    }, 1000);

    // 组件卸载时清理定时器
    return () => clearTimeout(timerBoss);
  }, [time, productId, id, expireIn, updateAddedProduct]);

  // 处理菜品过期逻辑
  const handleProductExpire = () => {
    const updatedProduct = {
      id: id,
      productId: productId,
      status: 1,
      timeProductExpired: expireIn,
    };
    updateAddedProduct(updatedProduct);
    // 删除LocalStorage键值
    window.localStorage.removeItem(productId);
  };

  // 格式化时间
  const getFormattedTime = (milliseconds) => {
    const hours = String(Math.floor((milliseconds % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60))).padStart(2, 0);
    const minutes = String(Math.floor((milliseconds % (1000 * 60 * 60)) / (1000 * 60))).padStart(2, 0);
    const seconds = String(Math.floor((milliseconds % (1000 * 60)) / 1000)).padStart(2, 0);
    return `${hours}:${minutes}:${seconds}`;
  };

  return <h5>{getFormattedTime(Math.max(time, 0))}</h5>;
}

export default TimeFunction;

关键优化点说明

  • 同步LocalStorage的时机:只在计时更新时写入,避免渲染阶段的无效写入覆盖删除操作。
  • 函数式状态更新:setTime(prev => ...)确保每次获取的都是最新的状态值,解决闭包导致的计时错误。
  • 统一初始化逻辑:合并了原有的if/else分支,代码更简洁易维护。
  • 过期逻辑抽离:把过期处理单独封装成函数,逻辑更清晰。
  • 定时器清理:useEffect的返回函数清除定时器,防止组件卸载后定时器继续运行。

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 02:07:39