История государства не обновляется должным образом в реакции

Я строю игру Ханойская башня, чтобы привыкнуть реагировать. У меня есть свойство состояния, называемое "дисками", которое представляет собой массив, состоящий из 3 массивов длины N (N - общее количество дисков). Я также определил свойство состояния "history", которое должно содержать историю массива дисков следующим образом:

  1. изначально: history = [диски (начальная конфигурация)]
  2. После 1 хода: history = [диски (начальная конфигурация), диски (после 1 хода)]
  3. Через 2 хода: history = [диски (начальная конфигурация), диски (после 1 хода), диски (после 2 хода)] и т. Д.

Тем не менее, после перемещения M, массив истории выглядит так:

history = [диски (после 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);
  }

Обратите внимание, что игра работает иначе, то есть массив дисков обновляется корректно и т. Д. Это просто обновление массива истории, которое как-то идет не так. Я попытался поместить диски.s.sice () в history.concat, так как мне показалось, что история каким-то образом хранит ссылки на массив дисков, но это не помогло.

1 ответ

Решение

Проблема исходит из этого:

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

Это мутирует disk по указателю i на месте и мутирует выбранный 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
  };
}

Вы видите, что я ввел несколько функций для разных частей isLegalMove а также updateDiskони должны разделить проблемы и сделать тестирование более простым.

Примечание относительно использования Array.prototype.slice: как вы заметили, это делает только поверхностную копию массива, это означает, что если у вас есть вложенный объект, и вы делаете только поверхностную копию внешнего, а затем изменяете его, оригинальная копия также будет видоизменяться. Вы можете создать deepCopy вместо.

Другие вопросы по тегам