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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 11:15:30