나는 최근에 리액트 공식문서를 따라하면서 간단한 tic-tac-toe 게임을 구현했다. 해당 문서에 제시된 코드엔 Square, Board, Game 컴포넌트가 있는데, Board와 Game은 클래스형으로 되어있었다. 그래서 이를 함수형으로 바꾸고자 한다!
함수형 컴포넌트란, 이름 그대로 함수로 정의된 리액트 컴포넌트이다. 클래스로 정의된 컴포넌트보다 코드가 짧고 예상치 못한 버그를 방지할 수 있기 때문에 사용이 권고된다.
리액트 Hooks는 함수로 state를 연결(hook into) 하고 바로 함수 컴포넌트에 라이프사이클 기능을 연결 할 수 있게 해준다. Hooks는 할 말이 많은데 우선은 tic-tac-toe 예제를 함수형으로 바꾸기 위해 필요한 것만 언급할 것이다.
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>
);
}
원래 클래스형 컴포넌트 일 때의 코드는 이곳에서 확인 가능하다. 아래 코드와 비교하며 같이 보면 이해에 큰 도움이 될 것이다!
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 컴포넌트를 함수형으로 바꾼 코드는 아래 코드와 같다. 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>
);
}