Frontend/React

[React] TicTacToe앱 만들기(1) - 컴포넌트 생성, props, state

gamzaggang7 2024. 6. 14. 00:04
728x90

1. 기본 설정

택택톡 앱은 크게 게임판 부분과 게임 정보 부분으로 구성된다.

App.js

function App() {
  return (
    <>
      <div className="game">
        <div className="game-board">

        </div>
        <div className="game-info">

        </div>
      </div>
    </>
  );
}

export default App;

 

App.css

.game{
  display: flex;
  flex-direction: row;
}

.game-info {
  margin-left: 20px;
}

 

index.css

body {
  font: 14px "Century Gothic", Futura, sans-serif;
  margin: 20px;
}

ol, ul {
  padding-left: 30px;
}

 

2. Board, Square 컴포넌트 생성

컴포넌트 폴더를 생성하고 그 안에 Board와 Square 컴포넌트를 생성한다.

Reactjs Code Snippet 확장 프로그램을 설치했다면 단축어로 컴포넌트 코드를 간편하게 생성할 수 있다.

함수형 컴포넌트는 rsc, 클래스형 컴포넌트는 rcc을 입력하면 된다.

import React, { Component } from 'react';

class Board extends Component {
  render() {
    return (
      <div>
       
      </div>
    );
  }
}

export default Board;
import React, { Component } from 'react';

class Square extends Component {
  render() {
    return (
      <div>
       
      </div>
    );
  }
}

export default Square;

 

Square.js는 board를 구성하는 각 칸이고 각 칸은 버튼으로 만든다.

class Square extends Component {
  render() {
    return (
      <button className='square'>
        Square
      </button>
    );
  }
}

 

Board.js에서 Square를 연결하는데 Square 컴포넌트를 바로 연결하지 않고, Square를 리턴하는 함수를 만들어서 그 함수를 호출하도록 한다. 함수는 몇번째 Square인지를 인자로 받는다.

0 1 2
3 4 5
6 7 8
class Board extends Component {
  renderSquare(i) {
    return <Square />
  }
  render() {
    return (
      <div>
        <div className="status">Next Player: X, O</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>
    )
  }
}

 

이제 App.js에 Board 컴포넌트를 연결한다.

function App() {
  return (
    <>
      <div className="game">
        <div className="game-board">
          <Board />
        </div>
        <div className="game-info">
          game-info
        </div>
      </div>
    </>
  );
}

 

Board, Square 스타일링)

각각의 파일에서 css파일을 import한다.

import "./Board.css";
import "./Square.css";

 

Square.js

.square {
  background-color: #fff;
  border: 1px solid #999;
  font-size: 24px;
  height: 34px;
  margin-right: -1px;
  margin-top: -5px;
  padding: 0;
  text-align: center;
  width: 34px;
}

 

Board.js

.status {
  margin-bottom: 10px;
}

 

3. 데이터 전달(Props)

board컴포넌트에서 square컴포넌트로 칸 번호를 넘겨준다. props를 렌더링할 때는 JSX에서 {}로 감싸주면 된다.

class Board extends Component {
  renderSquare(i) {
    return <Square value={i} />
  }
class Square extends Component {
  render() {
    return (
      <button className='square'>
        {this.props.value}
      </button>

props는 부모 컴포넌트에서만 설정이 가능하며 컴포넌트 자신은 props를 읽기 전용으로만 사용할 수 있다.

 

4. State 추가

state는 컴포넌트 내부에서 바뀔 수 있는 값이다. state가 변경되면 컴포넌트는 리렌더링되며 state는 컴포넌트 안에서 관리된다.

컴포넌트에 state를 설정할 때는 constructor메서드를 작성하여 설정하며, 클래스형 컴포넌트에서 constructor를 작성할 때는 super(porps)를 호출해야 한다.

class Square extends Component {
  constructor(props) {
    super(props);
    this.state = {
      value: null
    }
  }
  render() {
    return (
      <button className='square' onClick={() => console.log('click')}>
        {this.state.value}
      </button>
    );
  }
}

이렇게 하면 칸의 값은 null이므로 아무것도 안보이게 된다.

칸을 클릭하면 X가 나타나도록 setState를 사용해 state 값을 업데이트한다.

<button className='square' onClick={() => this.setState({ value: 'X' })}>

 

여러 자식으로부터 데이터를 받거나 두 자식이 서로 통신하도록 하려면 부모 컴포넌트에 공유 state를 정의해야 한다. 부모 컴포넌트는 props를 사용해 자식 컴포넌트에 state를 다시 전달할 수 있고, 이는 자식 컴포넌트들이 서로 또는 부모 컴포넌트와 동기화하도록 만든다.

위에서 추가한 state를 부모 컴포넌트인 Board.js로 옮긴다. 9칸의 사각형에 해당하는 길이가 9인 배열을 초기 state로 설정한다.

class Board extends Component {
  constructor(props) {
    super(props);
    this.state = {
     squares: Array(9).fill(null)
    }
  }

renderSquare 메서드는 Squares 배열의 인덱스를 반환하도록 한다.

  renderSquare(i) {
    return <Square value={this.state.squares[i]} />
  }

Square 컴포넌트에서 state를 삭제했으므로 this.state를 this.props로 수정한다.

class Square extends Component {
  render() {
    return (
      <button className='square' onClick={() => this.setState({ value: 'X' })}>
        {this.props.value}
      </button>
    );
  }
}

 

Board 컴포넌트에 square값을 'X'로 바꾸는 함수를 추가한다.

  handleClick(i) {
    const squares = this.state.squares.slice();
    squares[i] = 'X';
    this.setState({ squares: squares })
  }


스퀘어 값을 반환하는 renderSquare 메서드에서 클릭하면 handleClick 메서드를 호출하도록 한다.

  renderSquare(i) {
    return <Square value={this.state.squares[i]} onClick={() => this.handleClick(i)} />
  }

 

class Square extends Component {
  render() {
    return (
      <button className='square' onClick={() => { this.props.onClick() }}>
        {this.props.value}
      </button>
    );
  }
}

 

  1. 버튼을 클릭하면 onClick 이벤트 핸들러를 호출하고 이벤트 핸들러는 this.props.onClick()을 호출한다.
  2. Board에서 Square로 onClick={() => this.handleClick(i)}를 전달하기 때문에 Square를 클릭하면 Board의 handleClick(i)를 호출한다.

728x90