디발자의 Engineering Wiki
React 동작 실행 흐름 본문
import GameBoard from "./components/GameBoard"
import Log from "./components/Log";
import Player from "./components/Player"
import GameOver from "./components/GameOver.jsx";
import { useState } from "react"
import { WINNING_COMBINATIONS } from './winning-combinations.js';
const PLAYERS = {
X: 'Player 1',
O: 'Player 2'
};
const INITIAL_GAME_BOARD = [
[null, null, null],
[null, null, null],
[null, null, null],
]
// 현재 플레이어
function deriveActivePlayer(gameTurns) {
let currentPlayer = 'X';
if(gameTurns.length > 0 && gameTurns[0].player === 'X') { // 앞에 플레이한 사람이 X라면, O로 변경
currentPlayer = 'O';
}
return currentPlayer;
}
// 게임턴
function deriveGameBoard(gameTurns) {
let gameBoard = [...INITIAL_GAME_BOARD.map(array => [...array])];
for(const turn of gameTurns) {
const{ square, player } = turn;
const { row, col } = square;
gameBoard[row][col] = player;
}
return gameBoard;
}
// 우승여부 체크
function deriveWinner(gameBoard, players) {
let winner;
for (const combination of WINNING_COMBINATIONS) {
const firstSquareSymbol = gameBoard[combination[0].row][combination[0].column];
const secondSquareSymbol = gameBoard[combination[1].row][combination[1].column];
const thirdSquareSymbol = gameBoard[combination[2].row][combination[2].column];
if(firstSquareSymbol &&
firstSquareSymbol === secondSquareSymbol &&
firstSquareSymbol === thirdSquareSymbol) {
winner = players[firstSquareSymbol];
}
}
return winner;
}
function App() {
// 게임오버시 플레이어 이름이 나오도록 하기 위해 정의하기
const [players, setPlayers] = useState(PLAYERS);
// Log에 저장할 게임 턴 진행 상태
const [gameTurns, setGameTurns] = useState([]);
const activePlayer = deriveActivePlayer(gameTurns); // 현재 턴의 플레이어(app 바깥쪽의 함수로 사용)
const gameBoard = deriveGameBoard(gameTurns); // 게임 턴(app 바깥쪽의 함수로 사용)
const winner = deriveWinner(gameBoard, players); // 승리. (app 바깥쪽의 함수로 사용)
const hasDraw = gameTurns.length === 9 && !winner; // 무승부. 게임턴이 9번이 지났고 ㅡ승자가 없을 시
// setActivePlayer 함수를 통해 activePlayer 을
// X, O 번갈아가며 선택되도록 하는 함수
function handleSelectSquare(rowIndex, colIndex) {
setGameTurns((prevTurns) => {
const currentPlayer = deriveActivePlayer(prevTurns);
const updateTurns = [
{ square: { row: rowIndex, col: colIndex }, player: currentPlayer},
...prevTurns,
];
return updateTurns;
});
}
// rematch
function handleRestart() {
setGameTurns([]); // 게임 배열을 빈 배열로 만듬. 초기화
}
// 플레이어 이름이 변경되었을 시 함수. 게임오버시 변경된 플레이어 이름이 출력되어야해서 필요함
function handleplayerNameChange(symbol, newName) {
setPlayers(prevPlayers => {
return {
...prevPlayers,
[symbol]: newName
};
});
}
return (
<main>
<div id="game-container">
<ol id="players" className="highlight-player">
<Player
initialName={PLAYERS.X}
symbol="X"
isActive={activePlayer === 'X'}
onChangeName={handleplayerNameChange}
/>
<Player
initialName={PLAYERS.O}
symbol="O"
isActive={activePlayer === 'O'}
onChangeName={handleplayerNameChange}
/>
</ol>
{(winner || hasDraw) && (
<GameOver winner={winner} onRestart={handleRestart}/>
)}
<GameBoard
onSelectSquare={handleSelectSquare}
board={gameBoard}
/>
</div>
<Log turns={gameTurns}/>
</main>
)
}
export default App
리액트 웹사이트의 실행 흐름을 Udemy강의 예제로 정리해보았다.
다른 컴포넌트도 있지만 간단하게 메인 App컴포넌트만 가져와서 흐름을 보면,
기본적인 동작 실행 흐름
1. 웹사이트 실행
- App 컴포넌트 렌더링
- 컴포넌트 외부함수들이 정의됨
더보기
외부함수를 쓰는 이유
1) 컴포넌트가 재렌더링될 때마다 이 함수들이 재생성되지 않는다. 한번 정의해두고 이벤트 발생으로 함수가 필요할 때 가져다 쓴다.
2) 외부 컴포넌트에서 함수를 가져다 쓸 수 있다.
3) 순수한 로직 함수들을 UI 컴포넌트와 분리. -> Spring의 MVC나 boot의 service 처럼 UI와 로직을 분리한거라고 생각했다.
2. App 컴포넌트 실행
- useState 실행으로 초기값 설정
- 외부함수를 사용하여 현재 상태 계산
- return 문 안의 JSX 실행으로 컴포넌트 렌더링
- 자식 컴포넌트(e.g. GameBoard)에 props로 함수 전달(e.g. onSelectSquare)
3. 사용자와 상호작용
- 이벤트 발생으로 함수 실행
4. 상태업데이트 및 재랜더링
- 상태 업데이트(e.g. setGameTurns)
- 상태 업데이트로 인한 App 컴포넌트 재랜더링(외부함수들은 유지)
- 2번부터 다시 실행 ... 반복
리액트 흐름이 종종 헷갈려서 메인 컴포넌트의 동작을 간단히 기록해보았다.