FE/React

[React] 틱택토 추가 기능 구현하기 (2)

Juliie 2021. 8. 17. 22:01
잠깐!✋ 이 포스팅은 React 자습서를 따라해보고 복습을 목적으로 하는 포스팅입니다
틀린 부분이 있으면 정정 부탁드립니다!
리액트 자습서(틱택토 만들기) 링크: https://reactjs.org/tutorial/tutorial.html
 

Tutorial: Intro to React – React

A JavaScript library for building user interfaces

reactjs.org

4. 오름차순이나 내림차순으로 이동을 정렬하도록 토글 버튼을 추가해주세요.

(Add a toggle button that lets you sort the moves in either ascending or descending order.)

 

이게 제일 어려웠다 (내 기준🥲)

첫번째로 생각했던건 정렬 방식이 변경될때마다 state 내 history를 직접 reverse 하는 거였는데 이 방식대로 하면

  • state 데이터를 너무 많이 건드리게 된다
  • current도 연쇄적으로 current = history[0]로 바꿔야한다
    • 내림차순의 경우에 인덱스가 0이라서 저장이 제대로 안되는 문제
    • 오름차순과 내림차순의 ox가 따로 노는 문제
    • 내림차순으로 변경하고 사각형을 클릭하면 그 부분부터 다시 오름차순이 되는 문제

등등...복잡해져서 두번째 방법을 시도했다

class Game extends React.Component {
  constructor(props) {
  ...(생략)...
      stepNumber: 0,
      xIsNext: true,
      isAsc: true, /*추가된 부분*/
    };
  }

1) Game 컴포넌트의 생성자에서 정렬 방식이 토글될 때마다 상태를 저장하는 isAsc을 선언한다(기본값은 isAsc: true ➡️ 오름차순 상태)

 

/*Game 컴포넌트 내*/
setAsc() {
    this.setState({
      isAsc: !this.state.isAsc,
    });
  }

2) 정렬 상태를 토글해주는 메소드 setAsc를 선언한다

 

render() {
    let history = this.state.history; /*변경된 부분*/
    const current = history[this.state.stepNumber];
    const winner = calculateWinner(current.squares);
    if (!this.state.isAsc) history = history.slice().reverse(); /*추가된 부분*/
    const moves = history.map((step, move) => {
      move = this.state.isAsc ? move : history.length - 1 - move; /*추가된 부분*/
      const item = /*변수 이름 변경*/
        step.lastIndex >= 0 /*조건 수정*/
          ? "Go to move #" +
            move +
            " (" +
           ...(생략)

3. 정렬 조건이 내림차순이라면 history 배열을 reverse하고, move를 내림차순의 인덱스로 바꿔준다

(const는 값을 변경할 수 없어서 let으로 수정...🥲)

4. move = 0 or 그 밖의 경우 대신 step에서 lastIndex(해당 턴에서 선택된 square의 인덱스)가 -1일 경우 or 그 밖의 경우로 나눴다

 

 <div className="game-info">
          <div>{status}</div>
          <button onClick={() => this.setAsc()}>
            {this.state.isAsc ? "오름차순" : "내림차순"}
          </button>
          <ol {...(this.state.isAsc ? {} : { reversed: true })}>{moves}</ol>
        </div>

5. 정렬 조건 토글 버튼을 추가한다

6. 정렬 조건에 따라서 ol을 역순으로 시작한다


5. 승자가 정해지면 승부의 원인이 개의 사각형을 강조해주세요.

(When someone wins, highlight the three squares that caused the win.)

 

처음엔 단순히 classList 받아와서 조건에 맞으면 바로 특정 클래스를 추가하는 방식으로 구현했는데!

다른 step으로 점프해도 색이 원래대로 돌아오지 않는 문제가 있었다😂

그래서 state에 colored라는 변수를 추가하고 true/false로 구별하기로 방향을 바꿨다

 

function calculateWinner(squares) {
  ...(생략)...
   if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
      return { who: squares[a], where: [a, b, c] };
    }
  }

1. calculateWinner이 승리한 사람과 사각형의 위치를 모두 return하도록 수정한다

 

class Game extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      history: [
        {
          squares: Array(9).fill(null),
          lastIndex: -1,
          colored: Array(9).fill(false), /* 새롭게 추가 */
        },
      ],...(생략)

2. 승부의 원인이 되는 사각형을 체크할 colored 변수를 선언한다

 

/* Square 컴포넌트 내 */
<button
      className={"square " + (props.val_color ? "colored" : "")}
      onClick={props.onClick}
    >

3. 조건부로 colored 클래스를 추가해준다

 

※ colored 클래스는 제가 임의로 설정한 이름입니다(배경색만 변경해줌)

// index.CSS
.colored {
  background-color: pink;
}

 

if (calculateWinner(squares) || squares[i]) {
      //이미 winner가 나왔거나 이미 선택된 사각형일 때
      return;
    }
    squares[i] = this.state.xIsNext ? "X" : "O";

    if (calculateWinner(squares)) {
      //결과가 나왔을 때
      let temp = calculateWinner(squares).where;
      for (let i = 0; i < temp.length; i++) {
        colored[temp[i]] = true;
      }
    }

4. 승부가 났을때 특정 인덱스의 colored를 true로 바꾼다

 


6. 승자가 없는 경우 무승부라는 메시지를 표시해주세요.

(When no one wins, display a message about the result being a draw.)

 

function calculateWinner(squares) {
  ...(생략)...
  if (squares.filter((e) => e === null).length === 0) /* 추가된 */
    return { who: "D", where: 0 }; /* 부분 */
  return null;
}

 

1. 현재 squares에서 승자가 나오지 않았지만 null이 하나도 없는 상태이면 "D"를 리턴한다

(굳이 who와 where를 나눈 이유는 다른 곳에서 where를 쓰는 코드가 있기 때문에...where를 리턴 안해주면 null 오류가 난다😭

그래서 for문이 돌지 않게 0으로 리턴해줬다)

 

//Game 컴포넌트 내
let status;
    if (winner) {
      status = "Winner: " + winner.who;
      if (winner.who == "D") status = "DRAW"; /* 수정된 */
      else status = "Winner: " + winner.who; /* 부분 */
    } else {
      status = "Next player: " + (this.state.xIsNext ? "X" : "O");
    }

2. winner의 리턴값이 있을 경우, 다시 "D"인 경우와 다른 경우로 나눠서 status에 값을 넣는다

 

 

끝~~ 이제 리팩토링만 남았다 😆