디발자의 Engineering Wiki

React 동작 실행 흐름 본문

Engineering/React

React 동작 실행 흐름

bbrotngus 2024. 10. 9. 23:17
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번부터 다시 실행 ... 반복

 


 

리액트 흐름이 종종 헷갈려서 메인 컴포넌트의 동작을 간단히 기록해보았다.