React用localStorage持久化组件切换时的猫咪签到状态
React路由切换后状态丢失,使用localStorage持久化猫咪签到状态
问题根因
你现在的代码有三个核心问题导致状态丢失:
- localStorage存储逻辑和签到状态完全脱节,存的是无意义的默认值,根本没有保存实际的签到数据
- 大量直接操作DOM修改按钮文本、样式的写法不符合React状态驱动的设计,组件卸载重挂后DOM会重置为初始模板内容,自然看不到之前的状态
- 没有在组件加载时读取本地存储的内容还原状态
修复方案
按照下面的逻辑改就可以实现状态持久化:
- 用统一的对象结构存储所有猫咪的签到状态,方便序列化存入localStorage
- 组件初始化时优先读取localStorage里的存储值作为状态初始值
- 监听签到状态变化,每次更新自动同步到localStorage
- 所有UI展示(按钮文字、状态文本、按钮颜色)全部由状态驱动,不要直接操作DOM
完整修复代码
import "./CatSign.css"; import React, { useState, useEffect } from "react"; import SignedIn from "./SignedIn.js"; import whiteCat from "./Pics/whiteCat.jpg"; import spotty from "./Pics/spotty.jpg"; import small from "./Pics/small.jpg"; import clouded from "./Pics/cloudedleopard.jpg"; import leopard from "./Pics/leopard.jpg"; import tiger from "./Pics/tiger.jpg"; import whiteTiger from "./Pics/whiteTiger.jpg"; // 本地存储key,抽成常量避免写错 const STORAGE_KEY = "cat_signin_records"; const catList = ["Mittens", "Bao", "Amber", "Tash", "Kasia", "Luna", "Spike"]; const catPhotos = [whiteCat, spotty, small, clouded, leopard, tiger, whiteTiger]; // 生成默认签到状态:所有猫咪默认未签到 const initDefaultStatus = () => { return catList.reduce((res, name) => { res[name] = false; return res; }, {}); }; export default function CatSign(props) { const [listVisible, setListVisible] = useState(true); const [activeCat, setActiveCat] = useState(null); // 初始化状态:优先读本地存储,没有就用默认值 const [signRecords, setSignRecords] = useState(() => { const saved = localStorage.getItem(STORAGE_KEY); return saved ? JSON.parse(saved) : initDefaultStatus(); }); // 状态变化自动同步到本地存储 useEffect(() => { localStorage.setItem(STORAGE_KEY, JSON.stringify(signRecords)); }, [signRecords]); // 切换单只猫咪签到状态 const toggleCatSign = (catName) => { const newStatus = !signRecords[catName]; setSignRecords(prev => ({...prev, [catName]: newStatus})); if (newStatus) { setActiveCat(catName); setListVisible(false); } else { setActiveCat(null); setListVisible(true); } }; if (!props.dailyLog) return null; return ( <div className="CatSign"> <div className="catsExpected" style={{ display: listVisible ? "block" : "none" }} > You have {catList.length} cats expected today </div> {catList.map((catName, index) => { const signedIn = signRecords[catName]; return ( <div className="childContainer" key={catName} style={{ display: listVisible ? "flex" : "none" }} > <img className="catPic" src={catPhotos[index]} alt="Cat"></img> <p className="cat"> {catName} </p> <p className="status">{signedIn ? "Signed In" : "Signed Out"}</p> <button className="signInOut" onClick={() => toggleCatSign(catName)} style={{ background: signedIn ? "#feffff" : "#f0677a" }} > {signedIn ? "Sign Out" : "Sign In"} </button> </div> ); })} <SignedIn signIn={!!activeCat} setSignIn={(isSignIn) => { if (!isSignIn && activeCat) { setSignRecords(prev => ({...prev, [activeCat]: false})); setActiveCat(null); setListVisible(true); } }} cat={activeCat} setIsShown={setListVisible} /> </div> ); }
额外说明
- useState初始化时用函数读取localStorage,能避免组件每次渲染都重复读取本地存储,性能更好
- 不要依赖DOM内容判断当前状态(比如你之前判断按钮innerText是不是Sign In),所有状态都存在React state里,UI完全跟着状态走,就不会出现切换页面后状态重置的问题
- 现在不管怎么切换路由组件,只要浏览器本地存储不清除,签到状态都会一直保留
内容的提问来源于stack exchange,提问作者AlexPD93
相关产品推荐
相关产品推荐

