React删除列表元素后Input值显示异常问题求助
嘿,我来帮你拆解下这个输入框显示异常的问题,主要是两个核心原因导致的:
1. 列表渲染没加唯一key,React错误复用了DOM元素
这是输入框显示重复的最关键原因。React渲染列表时,需要给每个列表项指定唯一且稳定的key,不然它会默认用数组索引当key。当你删掉Player A后,剩下的B、C、D的索引都往前挪了一位,React的diff算法会误以为“原来索引0的DOM元素(显示A的输入框)现在对应新的索引0的B”,于是直接复用了那个输入框。但输入框的defaultValue只有在首次挂载的时候才会生效,后续不会因为defaultValue的变化自动更新,所以就出现了输入框显示B、B、C这种重复的情况。
2. 直接修改原State,还写错了数组更新逻辑
你在删除方法里直接修改了从state里拿出来的player对象:
player.playerAlias.pop(alias)
这属于直接篡改原state的引用类型数据,React没法准确检测到state变化,很容易引发渲染不一致。而且你更新state数组的写法也有问题:
player: [...prevState.playerList.player, player]
这不是删除元素,而是把修改后的player追加到原数组后面,相当于旧的player还在数组里,新的又加进去了——虽然你说打印测试逻辑正常,但这其实是个隐患,会导致数组里出现重复对象。
现在给你具体的修复步骤:
第一步:给列表项加上唯一key
在map渲染player的时候,一定要加key,用每个player的idPlayer(只要它是唯一的就行):
{this.state.playerList.player.map((player) => { return ( <div key={player.idPlayer}> {/* 这里必须加唯一key */} <GenericCard cardTitle="Player"> <button type="button" className="close" aria-label="Close" onClick={() => this.deletePlayer(player)}> <span aria-hidden="true">×</span> </button> <br /> {player.playerName} <input type="text" defaultValue={player.playerName} style={{ width: '100%' }} onChange={this.handleChange} /> {/* 其他内容 */} </GenericCard> </div> ) })}
划重点:别用数组索引当key!尤其是列表有增删操作的时候,绝对会踩坑。
第二步:修复删除方法,遵循React的不可变原则
如果是删除整个player,正确的写法应该是过滤掉目标player,创建新数组,不能直接修改原state:
deletePlayer = (targetPlayer) => { this.setState(prevState => ({ ...prevState, playerList: { ...prevState.playerList, // 过滤掉要删除的player,生成新数组 player: prevState.playerList.player.filter(player => player.idPlayer !== targetPlayer.idPlayer) } })) }
如果你是要删除player里的某个alias(对应你原来的deleteAliasToList方法),也要创建新的对象和数组,不能直接改原对象:
deleteAliasToList = (targetPlayer, aliasToDelete) => { this.setState(prevState => ({ ...prevState, playerList: { ...prevState.playerList, player: prevState.playerList.player.map(player => { if (player.idPlayer === targetPlayer.idPlayer) { // 过滤掉要删除的alias,生成新数组 const newAliases = player.playerAlias.filter(alias => alias.name !== aliasToDelete.name) // 返回新的player对象,不碰原对象 return { ...player, playerAlias: newAliases } } return player }) } })) }
第三步:(可选)把输入框改成受控组件
如果希望输入框内容和state实时同步,建议用value替代defaultValue,做成受控组件,这样state变了输入框会自动更新:
<input type="text" name="playerName" value={player.playerName} style={{ width: '100%' }} onChange={(e) => this.handleChange(e, player.idPlayer)} />
然后修改handleChange方法,根据player的id来更新对应的名称:
handleChange = (e, playerId) => { const newName = e.target.value this.setState(prevState => ({ ...prevState, playerList: { ...prevState.playerList, player: prevState.playerList.player.map(player => { if (player.idPlayer === playerId) { return { ...player, playerName: newName } } return player }) } })) }
这样改完之后,不管是删除player还是alias,React都能正确识别每个列表项,输入框的显示也会和state保持一致,不会再出现重复的问题啦。
内容的提问来源于stack exchange,提问作者pedro almeida

