如何正确擦除 React 状态数组中的元素?



基于状态数组创建组件并为每个组件提供数组的 setState 属性会导致奇怪的行为。也许是由于并行状态更改?试试下面的代码段。单击方块应将其删除。相反,将始终删除最后创建的正方形。

问题似乎出在Square组件内的useEffect()。虽然在代码片段中它真的没有用,但我使用类似的东西来使元素可拖动。有人可以解释这种行为并提出解决方案吗?我觉得我在这里错过了一些关于 React 的重要内容。

function Square(props){
const [pos, setPos] = React.useState([props.top, props.left])
React.useEffect(() => {
props.setSquares(prevState => prevState.map((square, index) => index === props.index ? {...square, top: pos + 20} : square
))
}, [pos])
function deleteMe(){
props.setSquares(prevState => prevState.filter((square, index) => index !== props.index))
}
return <div onClick={deleteMe} 
style={{top: pos[0]+'px', left: pos[1]+'px'}}             
className='square'>
</div>
}
function App(){
const [squares, setSquares] = React.useState([
{top: 20, left: 0},{top: 20, left: 100},
{top: 20, left: 200},{top: 20, left: 300}])
React.useEffect(() => console.log('rerender', ...squares.map(s => s.left)))
return <div>{squares.map((square, index) => <Square 
setSquares={setSquares} index={index} 
top={square.top} left={square.left}/>)}</div>
}
ReactDOM.render(<App/>, document.getElementById('root'))
.square{
position: absolute;
width: 80px;
height: 80px;
background: orange;
}
<div id='root'></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>

编辑:正如尼克在评论中建议的那样,我更改了setSquare方法以使用prevState.mapfilter,以免不由自主地更改以前的状态。

不过问题仍然存在...总是最后一个方块在屏幕上消失。对每次重新渲染使用效果表明状态已正确更新。我在这里错过了什么?

您必须记住,spread 运算符是一个拷贝,这意味着以下代码中的newSquares[props.index].top = pos[0] + 20正在改变当前状态数组中的对象。

props.setSquares(prevState => {
const newSquares = [...prevState]
newSquares[props.index].top = pos[0] + 20 // this is mutating current state
return newSquares
})

解决此问题的一种选择是改为执行此操作:

props.setSquares(prevState => {
const newSquares = [...prevState]
newSquares[props.index] = { 
...newSquares[props.index],
top: pos[0] + 20
};
return newSquares
})

另一种选择是只映射现有状态,然后为要更改的元素返回新内容:

props.setSquares(prevState => {
return prevState.map((el, i) => {
return i === props.index ? { ...el, top: pos[0] + 20 } : el;
});
})