React使用useState更新数组时仅保留最新点击元素问题求解
问题原因及修复方案
核心问题梳理
- 状态存储位置错误
你把capturedPkm状态定义在了PokemonCard组件内部,这意味着每个宝可梦卡片都有独立的、互不共享的capturedPkm数组。点击A卡片更新的是A组件自己的数组,点击B卡片更新的是B组件自己的数组,自然每次只能看到当前点击的宝可梦。这个状态需要提升到所有PokemonCard的公共父组件中存储,再把状态和更新方法通过props传递给每个卡片。 - 数组更新逻辑错误
就算不考虑状态位置的问题,你当前的更新逻辑也存在问题:
你已经基于旧数组拷贝得到了currentChecked,并且把当前宝可梦名push进去了,更新的时候直接把这个数组传给setCapturedPkm即可,原写法[...capturedPkm, currentChecked]相当于把整个新数组作为元素塞进旧数组,会得到嵌套结构,完全不符合需求;同时你没有处理取消选中的场景,取消勾选时没有把对应宝可梦从数组中移除;另外React的状态更新是异步的,调用setCapturedPkm后立刻打印capturedPkm拿到的还是更新前的旧值,不能用来验证更新结果。 - 冗余状态问题
你定义了notCapturedPkm但全程没用到,可以直接删掉;也不需要单独存储label状态,直接根据当前宝可梦是否在捕获数组中动态计算即可。
修复后代码
公共父组件示例
// 父组件示例,比如PokemonList.js import React, { useState } from 'react' import PokemonCard from './PokemonCard' const PokemonList = () => { // 把捕获状态移到父组件,所有卡片共享 const [capturedPkm, setCapturedPkm] = useState([]) return ( <div className="pokemon-list"> {/* 渲染卡片时把共享状态和更新方法传下去,替换成你实际的接口地址列表循环渲染即可 */} <PokemonCard url="宝可梦接口地址1" capturedPkm={capturedPkm} setCapturedPkm={setCapturedPkm} /> <PokemonCard url="宝可梦接口地址2" capturedPkm={capturedPkm} setCapturedPkm={setCapturedPkm} /> </div> ) } export default PokemonList
修改后的PokemonCard组件
import React, { useState, useEffect } from 'react'; import { Link } from 'react-router-dom'; import PokemonIcon from './PokemonIcon'; // 接收父组件传的capturedPkm和setCapturedPkm const PokemonCard = (props) => { const { url, capturedPkm, setCapturedPkm } = props const [pokemonCard, setPokemonCard] = useState({}) // 动态判断当前宝可梦是否被捕获,不需要单独存label状态 const isCaptured = capturedPkm.includes(pokemonCard.name) const toggleCaptured = (checked) => { if (checked) { // 函数式更新保证拿到最新的状态值,追加新宝可梦名称 setCapturedPkm(prev => [...prev, pokemonCard.name]) } else { // 取消选中时过滤掉当前宝可梦 setCapturedPkm(prev => prev.filter(name => name !== pokemonCard.name)) } } const fetchingPokemonCard = async () => { const res = await fetch(url); const data = await res.json(); setPokemonCard(data) } useEffect(() => { fetchingPokemonCard() }, [url]) 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, capturedPkm }} style={{ textDecoration: 'none', color: '#000000' }}> <div style={{ padding: '20px', display: 'flex', justifyContent: 'center', alignItems: 'center' }} > <PokemonIcon img={pokemonCard.sprites?.['front_default']} /> </div> </Link> <div style={{ textAlign: 'center' }}> <h1 >{pokemonCard.name}</h1> <label > <input type='checkbox' checked={isCaptured} onChange={(e) => toggleCaptured(e.target.checked)} /> <span style={{ marginLeft: 8, cursor: 'pointer' }}> {isCaptured ? 'Captured!' : 'Not captured'} </span> </label> </div> </div> </> ) } export default PokemonCard
内容的提问来源于stack exchange,提问作者geek101
相关产品推荐
相关产品推荐

