Tic Tac Toe
React Native tutorial에 안내되어 있는 tic tac toe 게임을 구현하며 React Native의 기본적인 사용법을 알아볼 것이다.
function Square(props) {
return (
<button className="square" onClick={props.onClick}>
{props.value}
</button>
);
}
class Board extends React.Component {
renderSquare(i) {
return (
<Square
value={this.props.squares[i]}
onClick={() => this.props.onClick(i)}
/>
);
}
render() {
return (
<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>
);
}
}
class Game extends React.Component {
constructor(props) {
super(props);
this.state = {
history: [
{
squares: Array(9).fill(null)
}
],
stepNumber: 0,
xIsNext: true
};
}
handleClick(i) {
const history = this.state.history.slice(0, this.state.stepNumber + 1);
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({
history: history.concat([
{
squares: squares
}
]),
stepNumber: history.length,
xIsNext: !this.state.xIsNext
});
}
jumpTo(step) {
this.setState({
stepNumber: step,
xIsNext: (step % 2) === 0
});
}
render() {
const history = this.state.history;
const current = history[this.state.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={() => this.jumpTo(move)}>{desc}</button>
</li>
);
});
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>{moves}</ol>
</div>
</div>
);
}
}
// ========================================
ReactDOM.render(<Game />, document.getElementById("root"));
function calculateWinner(squares) {
const lines = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6]
];
for (let i = 0; i < lines.length; i++) {
const [a, b, c] = lines[i];
if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
return squares[a];
}
}
return null;
}
우선 react native는 함수형 컴포넌트와 class형 컴포넌트로 구분이 된다.
class형 컴포넌트는 class형태의 컴포넌트를 의미하며 위의 코드에서는 Board, Game 컴포넌트가 이에 해당한다.
컴포넌트의 life cycle API를 사용하거나 state를 사용해야 하는 상황에서는 class형 컴포넌트를 이용한다.
class형 컴포넌트는 함수를 가지며 각각의 state를 가질 수 있다. 각각의 state란 컴포넌트가 유지하고 있는 상태 및 값들이다.
class Board extends React.Component {
renderSquare(i) {
return (
<Square
value={this.props.squares[i]}
onClick={() => this.props.onClick(i)}
/>
);
}
render() {
return (
<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>
);
}
}
renderSquare() 함수는 매개변수인 i에 맞는 value값과 onClick을 props로 전달하여 Square컴포넌트를 생성해준다.
render() 함수는 화면에 실제적으로 보여지는 컴포넌트들을 return 해주는 함수이다.
한 줄에 3개의 Square가 있고, 3줄이 있는 격자 형태의 사격형(3X3)을 return 한다.
함수형 컴포넌트는 함수형태로 정의된 컴포넌트를 의미하며 위의 코드에서는 Square 컴포넌트가 이에 해당한다.
state나 life cycle를 사용하지 않는 간단한 컴포넌트들이 필요할 때 사용한다.
아래와 같은 선언적 함수를 사용하는 경우도 있고, 화살표 함수를 사용하는 경우도 있다.
하지만 Hooks를 이용하여 함수형 컴포넌트에서도 state를 사용할 수 있다.
Hooks는 리액트 v16.8에 새로 도입된 기능으로서, 함수형 컴포넌트에서도 상태 관리를 할 수 있는 useState, 그리고 렌더링 직후 작업을 설정하는 useEffect 등의 기능 등을 제공하여 기존의 함수형 컴포넌트에서 할 수 없었던 다양한 작업을 할 수 있게 해 준다.
function Square(props) {
return (
<button className="square" onClick={props.onClick}>
{props.value}
</button>
);
}
나머지 Game 컴포넌트는 게임의 전체적인 제어와 관리를 담당하는 컴포넌트이다.
class Game extends React.Component {
constructor(props) {
super(props);
this.state = {
history: [
{
squares: Array(9).fill(null)
}
],
stepNumber: 0,
xIsNext: true
};
}
handleClick(i) {
const history = this.state.history.slice(0, this.state.stepNumber + 1);
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({
history: history.concat([
{
squares: squares
}
]),
stepNumber: history.length,
xIsNext: !this.state.xIsNext
});
}
jumpTo(step) {
this.setState({
stepNumber: step,
xIsNext: (step % 2) === 0
});
}
render() {
const history = this.state.history;
const current = history[this.state.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={() => this.jumpTo(move)}>{desc}</button>
</li>
);
});
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>{moves}</ol>
</div>
</div>
);
}
}
constructor는 생성자이며 state에 있는 history는 현재까지의 board의 상태를 나타내는 것이며, stepNumber는 게임의 단계를 나타낸다. xIsNext는 X의 차례인지를 나타낸다.
handleClick()은 Square들이 클릭되었을 때 처리를 한다.
jumpTo()는 해당하는 단계까지의 상태로 돌아가게 한다
render()는 게임에 대한 정보와 사각형 게임틀, 진행 과정 등을 출력하여 보여준다.