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

React项目如何从映射URL中Fetch单条Pokemon卡片实现点击切换捕获状态

修复方案

核心改动说明

你当前的核心问题有两个:

  1. 全局仅用单一布尔值存储捕获状态,导致所有卡片状态同步,无法单独切换单张卡片的捕获状态
  2. App 中一次性拉取了所有宝可梦详情,但仅固定取索引 0 的数据,无法动态匹配对应卡片

改造后代码

App.jsx

import Header from './components/Header';
import Footer from './components/Footer';
import Pokedex from './pages/Pokedex';
import Pokemon from './pages/Pokemon';
import Dropdown from './components/Dropdown';
import { useState, useEffect } from 'react';
import { Routes, Route, BrowserRouter as Router } from 'react-router-dom';
import { PokemonContext } from './context/PokemonContext'

function App() {
  const [pokemons, setPokemons] = useState([]);
  // 用Set存储已捕获的宝可梦ID,替代原单一布尔值,可区分单张卡片状态
  const [capturedIds, setCapturedIds] = useState(new Set())
  const URL = 'https://pokeapi.co/api/v2/pokemon/?limit=151';
  // 存储所有宝可梦详情数据,可按需动态取指定索引的数据
  const [pokemonCards, setPokemonCards] = useState([])

  const fetchingPokemons = async () => {
    const res = await fetch(URL);
    const data = await res.json();
    setPokemons(data.results)
  }

  useEffect(() => {
    fetchingPokemons()
  }, [URL])

  const fetchingPokemonCards = async () => {
    const res = await Promise.all(pokemons.map(pokemon => fetch(pokemon.url).then(res=> res.json())));
    setPokemonCards(res)
  }

  useEffect(() => {
    if(pokemons.length > 0) {
      fetchingPokemonCards()
    }
  }, [pokemons])

  // 接收宝可梦ID作为参数,仅切换对应卡片的捕获状态
  const toggleCaptured = (pokemonId) => {
    setCapturedIds(prev => {
      const newSet = new Set(prev)
      newSet.has(pokemonId) ? newSet.delete(pokemonId) : newSet.add(pokemonId)
      return newSet
    })
  }

  const providerValue = {
    pokemons,
    pokemonCards,
    capturedIds,
    toggleCaptured,
  }

  return (
    <PokemonContext.Provider value={providerValue}>
      <Router>
        <Header />
        <Dropdown />
        <main className='main'>
          <Routes>
            <Route exact path="/" element={<Pokedex />} />
            <Route path="/pokemon/:id" element={<Pokemon />} />
          </Routes>
        </main>
        <Footer />
      </Router>
    </PokemonContext.Provider>
  )
}

export default App;

PokemonCard.jsx

import React, { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import PokemonIcon from './PokemonIcon';
import { useContext } from 'react';
import { PokemonContext } from '../context/PokemonContext';

const PokemonCard = (props) => {
    const { url } = props
    const { capturedIds, toggleCaptured } = useContext(PokemonContext)
    const [pokemonCard, setPokemonCard] = useState({})

    const fetchingPokemonCard = async () => {
        const res = await fetch(url);
        const data = await res.json();
        setPokemonCard(data)
    }

    useEffect(() => {
        fetchingPokemonCard()
    }, [url])

    // 判断当前卡片是否被捕获
    const isCaptured = pokemonCard.id ? capturedIds.has(pokemonCard.id) : false

    return (
        <>
            <div className='pokemon-card' style={{
                height: '250px',
                maxWidth: '250px',
                margin: '1rem',
                boxShadow: '5px 5px 5px 4px rgba(0, 0, 0, 0.3)',
                cursor: 'pointer',
            }} >
                <Link to={{ pathname: `/pokemon/${pokemonCard.id}` }} state={{ pokemon: pokemonCard, captured: isCaptured }} style={{ textDecoration: 'none', color: '#000000' }}>
                    <div style={{ padding: '20px', display: 'flex', justifyContent: 'center', alignItems: 'center' }} >
                        <img style={{
                            height: '100px',
                            width: '100px',
                        }} src={pokemonCard.sprites?.['front_default']} alt='pokemon' />
                    </div>
                </Link>
                <div style={{ textAlign: 'center' }}>
                    <h1 >{pokemonCard.name}</h1>
                    <label >
                        <input
                            type='checkbox'
                            checked={isCaptured}
                            onChange={() => toggleCaptured(pokemonCard.id)}
                        />
                        <span style={{ marginLeft: 8 }}>
                            {isCaptured ? '已捕获!' : '未捕获!'}
                        </span>
                    </label>
                </div>
            </div>
        </>
    )
}

export default PokemonCard

动态索引取值说明

如果需要动态获取指定索引的宝可梦详情,直接通过 pokemonCards[自定义索引] 取值即可,索引可通过路由参数、点击事件传参等方式自定义。如果不需要全局存储所有宝可梦详情,可直接删除 App 中 fetchingPokemonCards 相关逻辑,复用 PokemonCard 组件内单独拉取当前卡片详情的逻辑即可,无需额外处理索引问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.24 18:54:02