即使状态发生变化,反应也不会使组件发生变化

问题描述:

正如我在反应主页here中遵循教程一样。我想修改x-o游戏代码以在游戏中前进和后退。我包含下面的代码。即使状态发生变化,反应也不会使组件发生变化

历史被记录在历史列表中,pos是列表上当前选择的索引。你可以看到我有三个按钮:复位,向上,向下。当您按Reset时,我重置状态。当你按下时,我增加pos。当你按下时,我减少pos。我也显示pos来检查。

问题是pos正在更新,但组件没有重新渲染。为便于使用我包括heroku here

我在这里失踪了什么?

import React, { Component } from 'react'; 
import './App.css'; 


function Square(props){ 
    return (
     <div className="square" onClick={() => props.onClick()}> 
      {props.value} 
     </div> 
    ); 
} 

function calculateWinner(squares){ 
    const winners = [ 
     [0, 1, 2], 
     [3, 4, 5], 
     [6, 7, 8], 
     [0, 4, 8], 
     [2, 4, 6], 
     [0, 3, 6], 
     [1, 4, 7], 
     [2, 5, 8] 
    ]; 
    for(let i = 0; i < winners.length; i++){ 
     const [a, b, c] = winners[i]; 
     if(squares[a] && squares[b] === squares[a] && squares[c] === squares[a]){ 
      return squares[a]; 
     } 
    } 
    return null; 
} 

class Board extends Component{ 
    constructor() { 
     super(); 
     this.state = { 
      history: [Array(9).fill(null)], 
      pos : 0, 
      xIsNext: true 
     } 
    } 

    handleClick(i){ 
     const current = this.state.history[this.state.pos]; 
     if (current[i] || calculateWinner(current)) { 

     } else { 
      const history = this.state.history.slice(); 
      const squares = history[this.state.pos].slice(); 
      squares[i] = this.state.xIsNext ? "X" : "O"; 
      history[this.state.pos + 1] = squares; 
      this.setState({ 
       history: history, 
       pos: this.state.pos + 1, 
       xIsNext: !this.state.xIsNext 
      }); 
     } 
    } 

    clickReset(){ 
     this.setState(
      { 
       history: [Array(9).fill(null)], 
       pos : 0, 
       xIsNext: true 
      } 
     ); 
    } 
    clickUp(){ 
     let pos = this.state.pos; 
     if(pos < this.state.history.length) { 
      pos += 1; 
      this.setState(
       { 
        pos: pos, 
        xIsNext: !this.state.xIsNext 
       } 
      ); 
     } 
    } 
    clickDown(){ 
     let pos = this.state.pos; 
     if(pos > 0) { 
      pos -= 1; 
      this.setState(
       { 
        pos: pos, 
        xIsNext: !this.state.xIsNext 
       } 
      ); 
     } 
    } 
    renderSquare(i){ 
     let current = this.state.history[this.state.pos]; 
     return (
      <Square value={current[i]} onClick={() => this.handleClick(i)}/> 
     ) 
    } 

    render() { 
     let status ; 
     const pos = this.state.pos; 
     const current = this.state.history[pos]; 
     const winnerCal = calculateWinner(current); 
     if (winnerCal){ 
      status = "Winner : " + winnerCal; 
     } else { 
      status = "Next Play : " + (this.state.xIsNext ? "X" : "O"); 
     } 
     return (
      <div className="col"> 

       <div className="row justify-content-center"> 
        <div className="board-row"> 
         {this.renderSquare(0)} 
         {this.renderSquare(1)} 
         {this.renderSquare(2)} 
        </div> 
        <div className="w-100"></div> 
        <div className="board-row"> 
         {this.renderSquare(3)} 
         {this.renderSquare(4)} 
         {this.renderSquare(5)} 
        </div> 
        <div className="w-100"></div> 
        <div className="board-row"> 
         {this.renderSquare(6)} 
         {this.renderSquare(7)} 
         {this.renderSquare(8)} 
        </div> 
       </div> 
       <div className="row justify-content-center"> 
        {status} pos: {pos} 
       </div> 
       <div className="row-fluid"> 
        <button type="button" className="col-md-4 btn btn-warning" onClick={() => this.clickReset()}>Reset</button> 
        <button type="button" className="col-md-4 btn btn-danger" onClick={() => this.clickUp()}>up</button> 
        <button type="button" className="col-md-4 btn btn-info" onClick={() => this.clickDown()}>down</button> 
       </div> 
      </div> 
     ) 
    } 
} 
class App extends Component { 

    renderHeader(){ 
     return (
      <div className="row justify-content-center"> 
       <div className="col"></div> 
       <div className="col"> 
        <h3 className="row justify-content-center"> Lets build a xo game </h3> 
       </div> 
       <div className="col"></div> 
      </div> 
     ) 
    } 

    renderBoard(){ 
     return (
      <div className="row"> 
       <div className="col"></div> 
       <Board className="col"/> 
       <div className="col"></div> 
      </div> 
     ) 
    } 

    render() { 
     return (
      <div className="container"> 
       {this.renderHeader()} 
       {this.renderBoard()} 
      </div> 
     ); 
    } 
} 

export default App; 

编辑:

在handleClick

()改变

const squares = history[this.state.pos] 
.. 
history[history.length] = squares; 

const squares = history[this.state.pos].slice(); 
.. 
history[this.state.pos + 1] = squares; 
+0

我在渲染函数中放置了一个断点,每次单击按钮时都会调用它。 – paqash

+0

确定将问题更改为更准确的问题 – StarLord

你的做法是最有可能运作良好。问题是你的历史被改写

const squares = history[this.state.pos]; 
squares[i] = this.state.xIsNext ? "X" : "O"; 
history[history.length] = squares; 

导致历史[history.length]引用同一个对象作为历史[history.length - 1]等降至历史[0]

尝试使用

const squares = history[this.state.pos].slice(); 
+0

解决了问题,但为什么应该处理ClickClick()链接点击个别方块会影响应呈现的内容。当我点击“向上”或“向下”时,要更清楚一点,如果你可以说slice()在那里的重要性,那么handleClick()的依赖关系是一个onClick方法() – StarLord

+0

。这将非常有用 – StarLord

+0

您在handleClick()处更新了组件状态,并且状态更改触发了新的呈现。当你调用setState时,你改变整个棋盘的状态(根据this.state.history渲染)。 这个问题是由于修改了最初得到的同一个方格数组而导致的,它使历史集合中的所有数组都引用了同一个实例。为了让每个新的历史事件成为前一步的副本,我使用了slice来获得浅拷贝 – Igor