浏览器LocalStorage计时函数执行removeItem后未删除问题排查
问题分析与解决
核心问题
你遇到的LocalStorage键值无法删除,本质是组件渲染时的重复写入操作覆盖了删除动作:
- 不管是原代码的
if还是else分支,每次组件渲染都会执行window.localStorage.setItem(productId, time),哪怕你在useEffect里删除了键值,组件重新渲染时又会把当前的time值写回LocalStorage,导致删不掉。 - useEffect的逻辑存在闭包问题,
setTime(time - 1000)里的time始终是初始值,计时逻辑会出错;同时过期判断的时机不对,应该在计时更新后检查是否到0。 - 代码冗余,
if和else分支的逻辑几乎完全重复,容易导致维护问题。
修复步骤
- 移除渲染阶段的LocalStorage写入:只在初始化和计时更新时同步LocalStorage,避免每次渲染都覆盖。
- 修正计时逻辑的闭包问题:使用函数式更新
setTime(prev => prev - 1000)获取最新状态值。 - 统一逻辑分支:把初始化time的逻辑合并,避免重复代码。
- 调整过期判断时机:在计时触发后检查时间是否为0,再执行删除和状态更新操作。
- 清理定时器:在组件卸载时清除定时器,避免内存泄漏。
修复后的完整代码
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
相关产品推荐
相关产品推荐

