【问题标题】:How to properly erase an element from a React state array?如何正确擦除 React 状态数组中的元素?
【发布时间】:2020-02-06 23:26:20
【问题描述】:

基于状态数组创建组件并为每个组件赋予数组的 setState 属性会导致奇怪的行为。也许是由于并行状态变化?试试下面的 sn-p。单击一个正方形应将其删除。相反,总是会删除最后创建的方格。

问题似乎在于Square 组件中的useEffect()。虽然在 sn-p 中它真的没用,但我使用类似的东西来使元素可拖动。有人可以解释这种行为并提出解决方案吗?我觉得我在这里错过了一些关于 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>

编辑:正如尼克在 cmets 中建议的那样,我将 setSquare 方法更改为使用 prevState.mapfilter,以免不自觉地更改以前的状态。

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

【问题讨论】:

    标签: javascript reactjs react-state-management react-state


    【解决方案1】:

    您必须记住扩展运算符是一个副本,这意味着以下代码中的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;
      });
    })
    

    【讨论】:

    • 感谢您的建议。我认为浅拷贝不会有害,因为它发生在 setSquares 方法中,其中 prevState 只是一个局部变量,对吧?在这里混用prevState 不应该对当前状态产生影响。正确的副本(例如由map 生成)也会导致相同的行为。虽然在Apps 返回之前的console.log(squares) 显示正确的方格已从状态中删除,但屏幕上错误的方格消失了
    • 我不相信prevState 是状态对象的深层副本,即使在回调函数中也是如此——可以肯定的是,这会非常低效。
    • 我认为您在此示例中将重新分配和变异混为一谈。一个更恰当的例子是,如果您声明是一个对象并且您正在改变该对象的属性
    • 你当然是完全正确的。我稍微更改了codepen。我仍然相当确信在setState 中不能乱用prevState
    • prevState 以某种方式独立于当前状态的问题意味着 React 必须在后台进行深层复制,这将非常低效,因为 每个 组件依赖于你的一部分的状态会重新渲染
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-09-27
    • 1970-01-01
    • 2020-01-27
    • 2015-06-14
    • 2018-05-03
    • 1970-01-01
    相关资源
    最近更新 更多