如何在React的Context API中将收藏的Pokémon数组存入LocalStorage?
解决React Pokédex收藏数据持久化问题
你的核心问题在于直接修改普通对象而非React状态,导致数据变化无法触发组件重渲染和LocalStorage的更新,同时没有从LocalStorage加载初始数据。以下是重构后的实现方案:
一、重构CatchProvider,使用useState管理状态并实现持久化
将普通的pokeDatabase换成React的useState,这样状态变化会触发组件更新,同时通过useEffect监听状态变化自动同步到LocalStorage,初始化时从LocalStorage读取历史数据。
import React, { useState, useEffect } from "react"; import CatchContext from "./Context"; const CatchProvider = ({ children }) => { // 从LocalStorage加载初始收藏数据,默认空数组 const [caughtPokemons, setCaughtPokemons] = useState(() => { const saved = localStorage.getItem('pokemons'); return saved ? JSON.parse(saved) : []; }); // 监听收藏数据变化,自动保存到LocalStorage useEffect(() => { localStorage.setItem('pokemons', JSON.stringify(caughtPokemons)); }, [caughtPokemons]); // 添加收藏的方法 const addPokemon = (name) => { if (!caughtPokemons.includes(name)) { setCaughtPokemons(prev => [...prev, name]); } }; // 移除收藏的方法 const removePokemon = (name) => { setCaughtPokemons(prev => prev.filter(poke => poke !== name)); }; // 判断是否已收藏 const isPokemonCaught = (name) => { return caughtPokemons.includes(name); }; return ( <CatchContext.Provider value={{ caughtPokemons, addPokemon, removePokemon, isPokemonCaught }}> {children} </CatchContext.Provider> ); } export default CatchProvider;
二、更新Context定义(可选,让类型更清晰)
import { createContext } from "react"; const CatchContext = createContext({ caughtPokemons: [], addPokemon: () => {}, removePokemon: () => {}, isPokemonCaught: () => false }); export default CatchContext;
三、修正SinglePokemon组件的逻辑错误
去掉不必要的async,修正收藏判断逻辑,使用Context提供的方法操作数据:
import * as C from './styles'; import { useContext, useEffect, useState } from 'react'; import { useApi } from '../../hooks/useApi'; import { useNavigate, useParams } from 'react-router-dom'; import PokeInfo from '../../components/PokeInfo'; import AddCircleOutlineIcon from '@mui/icons-material/AddCircleOutline'; import DoNotDisturbOnIcon from '@mui/icons-material/DoNotDisturbOn'; import CatchContext from '../../context/Context'; const SinglePokemon = () => { const api = useApi(); const { caughtPokemons, addPokemon, removePokemon, isPokemonCaught } = useContext(CatchContext); const { name } = useParams(); const navigate = useNavigate(); const handleHompage = () => { navigate('/'); } const [loading, setLoading] = useState(false); const [imgDatabase, setImgDatabase] = useState(''); const [infoPokemon, setInfoPokemon] = useState(null); const [pokemonTypes, setPokemonTypes] = useState([]); const [isCatch, setIsCatch] = useState(false); useEffect(() => { const fetchSinglePokemon = async () => { setLoading(true); try { const pokemon = await api.getPokemon(name); setImgDatabase(pokemon.sprites); setInfoPokemon(pokemon); setPokemonTypes(pokemon.types); // 判断当前宝可梦是否已收藏 setIsCatch(isPokemonCaught(pokemon.name)); } catch (err) { console.error('获取宝可梦信息失败:', err); } finally { setLoading(false); } }; fetchSinglePokemon(); }, [name, api, isPokemonCaught]); const handleCatchAdd = () => { if (!isCatch && infoPokemon) { addPokemon(infoPokemon.name); setIsCatch(true); } }; const handleCatchRemove = () => { if (isCatch && infoPokemon) { removePokemon(infoPokemon.name); setIsCatch(false); } }; if (loading || !infoPokemon) { return <div>加载中...</div>; } return ( <C.Container> <PokeInfo name={infoPokemon.name} /> <C.Card> <C.Info> <C.Imgs> <img src={imgDatabase.front_default} alt={`${infoPokemon.name} 正面`} /> <img src={imgDatabase.back_default} alt={`${infoPokemon.name} 背面`} /> </C.Imgs> <h2 id='types'>Tipos</h2> {pokemonTypes.map(type => ( <C.Types key={type.type.name}> <h2>{type.type.name}</h2> </C.Types> ))} {isCatch ? ( <DoNotDisturbOnIcon id='iconCatched' onClick={handleCatchRemove}/> ): <AddCircleOutlineIcon id='icon' onClick={handleCatchAdd}/>} </C.Info> </C.Card> <C.Return> <button onClick={handleHompage}>Retornar a Pokédex</button> </C.Return> </C.Container> ) } export default SinglePokemon;
关键改进点说明
- 使用useState管理状态:替代普通对象,确保状态变化触发组件重渲染和LocalStorage同步
- 自动持久化:通过
useEffect监听caughtPokemons变化,自动保存到LocalStorage - 封装操作方法:避免组件直接修改状态,保证数据操作的一致性和可维护性
- 修正逻辑错误:
- 用
includes()替代数组索引判断是否收藏(原逻辑错误,数组存的是名称字符串,不是对象) - 去掉不必要的
async(LocalStorage操作是同步的) - 修复
toString()的错误使用(infoPokemon.name本身就是字符串) - 添加加载状态的边界处理
- 用
内容的提问来源于stack exchange,提问作者Routfin
相关产品推荐
相关产品推荐

