状态历史在反应中没有正确更新

state history not updating properly in react

我正在建造一座河内塔游戏来习惯反应。我有一个名为 "disks" 的状态 属性,它是由 3 个长度为 N 的数组组成的数组(N 是磁盘的总数)。我还定义了一个状态 属性 "history" 应该包含这样的磁盘阵列的历史:

  1. 最初:历史=[磁盘(初始配置)]
  2. 1 次移动后: 历史记录 = [磁盘(初始配置),磁盘(1 次移动后)]
  3. 2 次移动后: history = [disks(Initial config), disks(after 1 move), disks(after 2 move)] etc.

然而,M移动后,历史数组如下所示:

历史 = [磁盘(M 移动后),磁盘(M 移动后),...,磁盘(M 移动后)]。

我找不到我的错误。如果有人知道出了什么问题,将不胜感激。这是相关代码:

constructor(props){
    super(props);
    let disks = [
      [],
      [],
      []
    ];
    //Initially all disks in first tower
    for(let i=0; i<props.numberOfDisks; i++){
      disks[0].push(i);
    }

    this.state = {
      disks : disks,
      selected : null,
      move: 0,
      history: [disks]
    };
  }

  handleClick(i){
    const disks = this.state.disks.slice();
    const history = this.state.history.slice();
    let move = this.state.move;
    let selected = this.state.selected;
    //if user has not previously selected a tower or selects the same tower again
    if(selected===null || i===selected){
      selected = disks[i].length>0 && i!==selected ? i : null;
      this.setState({
        selected : selected
      });
      return;
    }
    //Check if move is legal
    //index is at bottom is 0 and the largest disk has id 0
    if(disks[i].length === 0 || disks[i][disks[i].length-1] < disks[selected][disks[selected].length-1]){
      //perform move
      disks[i].push(disks[selected].pop());
      move++;
      // I guess this is where it goes wrong, but I can't see why
      this.setState({
        history: history.concat([disks]),
        disks: disks,
        move: move
      });
    }
    this.setState({
      selected: null
    });
    console.log(this.state.history);
  }

请注意,游戏在其他方面正常运行,这意味着磁盘阵列正在正确更新等...只是历史阵列的更新出了点问题。我尝试将 disks.slice() 放入 history.concat 中,因为在我看来,历史以某种方式存储了对磁盘阵列的引用,但这没有帮助。

问题来源于此:

disks[i].push(disks[selected].pop());

这会改变索引 i 处的 disk 并改变选定的 disk。 因为您将这些引用存储在 history 中并不断添加引用 这些对象 history 你观察到的是你的游戏稳定。

为了更好地了解正在发生的事情,您可以尝试拆分 handleClick 方法分为几个部分。

function getNewState (oldState, selectedIndex) {
  if (oldState.selected === selectedIndex) {
    return oldState;
  }
  if (isLegalMove(oldState, selectedIndex)) {
    return {
      ...oldState,
      selected: selectedIndex, //updated the index
      disks: updateDisk(oldState.disks, selectedIndex),
      move: oldState.move + 1
    };
  }
  return {
    ...oldState,
    selected: null
  };
}

你看我为isLegalMoveupdateDisk的不同部分介绍了几个功能, 这些是为了分离关注点并使测试更容易。

关于使用 Array.prototype.slice 的注意事项:如您所见,它仅对 a 进行浅表复制 一个数组,这意味着如果你有一个嵌套对象并且只做外部对象的浅拷贝 然后在里面 mutate ,原来的副本也会发生变异。您可能想创建一个 deepCopy