React 공식문서 예제 함수형 컴포넌트로 변경하기

YUKI KIM·2021년 11월 29일
0

나는 최근에 리액트 공식문서를 따라하면서 간단한 tic-tac-toe 게임을 구현했다. 해당 문서에 제시된 코드엔 Square, Board, Game 컴포넌트가 있는데, Board와 Game은 클래스형으로 되어있었다. 그래서 이를 함수형으로 바꾸고자 한다!


본격적으로 바꾸기에 앞서...

함수형 컴포넌트의 개념

함수형 컴포넌트란, 이름 그대로 함수로 정의된 리액트 컴포넌트이다. 클래스로 정의된 컴포넌트보다 코드가 짧고 예상치 못한 버그를 방지할 수 있기 때문에 사용이 권고된다.

  1. return
    함수면 return 값이 있기 마련. 함수형 컴포넌트는 반드시 JSX를 반환해야 하기 때문에 render()를 써주지 않아도 된다.
  2. props
    props는 매개변수의 역할을 한다. 따라서 함수 컴포넌트의 매개변수로 props를 넣어주고 props.NAME으로 접근해주면 사용이 가능하다. (this 키워드를 사용하지 않아도 되는 걸 유의하기!)

Hooks의 개념

리액트 Hooks는 함수로 state를 연결(hook into) 하고 바로 함수 컴포넌트에 라이프사이클 기능을 연결 할 수 있게 해준다. Hooks는 할 말이 많은데 우선은 tic-tac-toe 예제를 함수형으로 바꾸기 위해 필요한 것만 언급할 것이다.

  1. useState
    useState를 호출하게 되면, 두개의 값(state와 setter)을 가진 배열을 리턴한다. 아래 예제를 보면 쉽게 이해할 수 있다.
import React, { useState } from 'react';

function Toggle() {
  const [isToggled, setIsToggled] = useState(true); //true가 초기값

  return (
    <button 
    onClick={() => isToggled ? setIsToggled(false) : setIsToggled(true)}>
    	{isToggled ? 'T' : 'F'}
    </button>
  );
}

tic-tac-toe 함수형으로 바꾸기

원래 클래스형 컴포넌트 일 때의 코드는 이곳에서 확인 가능하다. 아래 코드와 비교하며 같이 보면 이해에 큰 도움이 될 것이다!

Board 컴포넌트

Board 컴포넌트를 함수형으로 바꾼 코드는 아래 코드와 같다. 매개변수 자리에 있는 props와 render, this가 사라진 것에 주목하자.

function Board(props) {
  function renderSquare(i) {
    return (
      <Square
        value={props.squares[i]}
        onClick={() => props.onClick(i)}
      />
    );
  }

  return (
    <div>
      <div className="board-row">
        {renderSquare(0)}
        {renderSquare(1)}
        {renderSquare(2)}
      </div>
      <div className="board-row">
        {renderSquare(3)}
        {renderSquare(4)}
        {renderSquare(5)}
      </div>
      <div className="board-row">
        {renderSquare(6)}
        {renderSquare(7)}
        {renderSquare(8)}
      </div>
    </div>
  );
}

Game 컴포넌트

Game 컴포넌트를 함수형으로 바꾼 코드는 아래 코드와 같다. useState 사용에 집중해서 보면 좋을 거 같다. 클래스 내에서 constructor를 사용하는 것보다 훨씬 가독성이 좋다.

function Game(props) {
  const [history, setHistory] = useState(
    [ {squares: Array(9).fill(null),} ]
  );
  const [stepNumber, setStepNumber] = useState(0);
  const [xIsNext, setXIsNext] = useState(true);

  function handleClick(i) {
      const newHistory = history.slice(0, stepNumber + 1);
      const current = newHistory[newHistory.length - 1];
      const squares = current.squares.slice();
      if (calculateWinner(squares) || squares[i]) {
          return;
      }
      squares[i] = xIsNext ? 'X' : 'O';

      setHistory(newHistory.concat([{squares: squares,}]));
      setStepNumber(newHistory.length);
      setXIsNext(!xIsNext);
  }

  function jumpTo(step) {
    setStepNumber(step);
    setXIsNext((step % 2) === 0);
  }

  const current = history[stepNumber];
  const winner = calculateWinner(current.squares);

  const moves = history.map((step, move) => {
      const desc = move ?
          'Go to move #' + move :
          'Go to game start';
      return (
          <li key={move}>
              <button onClick={() => jumpTo(move)}>{desc}</button>
          </li>
      )
  })

  let status;
  if (winner) {
      status = 'Winner: ' + winner;
  } 
  else {
      status = 'Next player: ' + (xIsNext ? 'X' : 'O');
  }

  return (
    <div className="game">
      <div className="game-board">
        <Board
          squares={current.squares}
          onClick={(i) => handleClick(i)}
        />
      </div>
      <div className="game-info">
        <div>{status}</div>
        <ol>{moves}</ol>
      </div>
    </div>
  );
}

레퍼런스

profile
유키링と 욘데 쿠다사이

0개의 댓글