본문 바로가기
개발

[ReactJS] 튜토리얼(Tutorial) - 9

by 마스터누누 2017. 6. 11.
728x90
반응형

튜토리얼(Tutorial) - 9



히스토리 저장


틱택토 게임을 만들면서 불변성에 대한 이야기를 했었다.

불변성을 유지함으로써 리액트에서 가지는 이점들이 상당히 많았는데

이를 틱택토 게임에 구체적으로 적용해보도록하자.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
history = [
  {
    squares: [
      nullnullnull,
      nullnullnull,
      nullnullnull,
    ]
  },
  {
    squares: [
      nullnullnull,
      null'X'null,
      nullnullnull,
    ]
  },
  // ...
]
cs


이를 위해서 상태값을 저장해야 하는데 위의 코드와 같이 배열에 순차적으로 넣어두는 방법이 있다.

이렇게 되면 인덱스 값으로 해당 히스토리에 접근이 가능하다.




1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Game extends React.Component {
  constructor() {
    super();
    this.state = {
      history: [{
        squares: Array(9).fill(null),
      }],
      xIsNext: true,
    };
  }
 
  render() {
    return (
      <div className="game">
        <div className="game-board">
          <Board />
        </div>
        <div className="game-info">
          <div>{/* status */}</div>
          <ol>{/* TODO */}</ol>
        </div>
      </div>
    );
  }
}
cs


앞서 Lifting State Up에서도 말했듯이 상태값을 저장하는 것을 상위의 컴포넌트에게 위임한다.

따라서 Board의 상위 컴포넌트인 Game에서 히스토리 상태값과 차례(xIsNext)를 관리하게 된다.


이를 위해서 첫째로 Game 클래스 내부에서 Constructor로 상태값을 초기화 해야한다.

물론, 이 때도 명시적으로 super(); 키워드를 적어줘야한다.


우선은 State값을 저장 하는 것만 수정 해줬다.

Board로 State값을 넘기는건 추후 변경할 예정이다.




1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
class Board extends React.Component {
  handleClick(i) {
    const squares = this.state.squares.slice();
    if (calculateWinner(squares) || squares[i]) {
      return;
    }
    squares[i] = this.state.xIsNext ? 'X' : 'O';
    this.setState({
      squares: squares,
      xIsNext: !this.state.xIsNext,
    });
  }
 
  renderSquare(i) {
    return (
      <Square
        value={this.props.squares[i]}
        onClick={() => this.props.onClick(i)}
      />
    );
  }
 
  render() {
    const winner = calculateWinner(this.state.squares);
    let status;
    if (winner) {
      status = 'Winner: ' + winner;
    } else {
      status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
    }
 
    return (
      <div>
        <div className="status">{status}</div>
        <div className="board-row">
          {this.renderSquare(0)}
          {this.renderSquare(1)}
          {this.renderSquare(2)}
        </div>
        <div className="board-row">
          {this.renderSquare(3)}
          {this.renderSquare(4)}
          {this.renderSquare(5)}
        </div>
        <div className="board-row">
          {this.renderSquare(6)}
          {this.renderSquare(7)}
          {this.renderSquare(8)}
        </div>
      </div>
    );
  }
}
cs


상태값을 상위 컴포넌트인 Game에서 저장하고 있기 때문에 하위 컴포넌트인 Board도 변경되어야한다.

변경사항은 다음과 같다.


- constructor를 Board에서 삭제한다.

- Board의 renderSquare 메소드안의 this.state.squares[i]를 this.props.squares[i]로 변경한다.

- Board의 renderSquare 메소드 안의 this.handleClick(i)를 this.props.handleClick(i)으로 변경한다.





1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
  render() {
    const history = this.state.history;
    const current = history[history.length - 1];
    const winner = calculateWinner(current.squares);
 
    let status;
    if (winner) {
      status = 'Winner: ' + winner;
    } else {
      status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
    }
 
    return (
      <div className="game">
        <div className="game-board">
          <Board
            squares={current.squares}
            onClick={(i) => this.handleClick(i)}
          />
        </div>
        <div className="game-info">
          <div>{status}</div>
          <ol>{/* TODO */}</ol>
        </div>
      </div>
    );
  }
cs


이제 미뤄두었던 Game의 state 관리와 전달 부분을 완성할 차례이다.

Board의 render에 있던 순서 처리 코드를 그대로 옮기고 

history 배열에 저장된 값, onClick을 하위 컴포넌트에 전달한다.


Game이 상태를 랜더링 하기 때문에 <div className="status">{status}</div>와 상태를 계산하는 코드를

Board의 랜더링 기능에서 삭제할수 있다.




1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  handleClick(i) {
    const history = this.state.history;
    const current = history[history.length - 1];
    const squares = current.squares.slice();
    if (calculateWinner(squares) || squares[i]) {
      return;
    }
    squares[i] = this.state.xIsNext ? 'X' : 'O';
    this.setState({
      historyhistory.concat([{
        squares: squares
      }]),
      xIsNext: !this.state.xIsNext,
    });
  }
cs


다음으로 handleClick을 Board에서 떼어내 Game으로 옮긴다

그리고 Game의 state가 다르게 구조화 되므로 살짝 코드의 변경이 필요한데,

Game의 Click은 새 항목을 만들고 history 배열에 push가 가능하다.


이 시점에서 Board는 renderSquare와 render, 두개의 메소드만 필요하다.

상태를 초기화 하는 constructor와 클릭 핸들러는 Game 컴포넌트에 있어야한다.


반응형

댓글