PJT_1. TicTacToe Application_3

클릭 이벤트는 props로 아래로 전달되고, key로 리스트를 구분하며, stepNumber로 현재 위치를 바꿔 history를 잘라 새 상태를 만들어 React 시간여행을 구현한다


1. 클릭 시 함수 호출 흐름

Photo of datatype

Function Call Flowon Click

JayTak



2. move는 좌표, state는 스위치: React 시간여행의 핵심 원리

2.1 move고유한 값을 준다

  const moves = history.map((step, move) => {
    const desc = move ?
      'Go to move #' + move :
      'Go to game start';
    return (
      <li key={move}>
        <button className="move-button" onClick={() => jumpTo(move)}>{desc}</button>
      </li>
    )
  })
  • move = history의 index
  • 0, 1, 2, 3 … → 절대 겹치지 않음
  • 에서 key로 사용 가능
<li key={move}>
// “이 버튼은 몇 번째 상태를 가리키는지 명확하다”


2.2 상태값이 바뀐다 (setStepNumber, setXIsNext)

  • 버튼 클릭
  • jumpTo(move) 실행
  • state 변경
  const jumpTo = (step) => {
    setStepNumber(step);
    setXIsNext((step % 2) === 0);
  }

  • 📌 이 순간:

    “현재 상태가 바뀌었다”고 React가 인식



3. React는 리스트를 업데이트할 때 key로 “누가 누구인지” 판단한다

3.1 key가 없을 때 (경고가 뜨는 이유)

<ul>
  <li>haha</li>
  <li>hoho</li>
</ul>

React의 문제

  • 리스트 항목이 여러 개인데
  • 구분할 이름표(key)가 없음

그래서 React는 경고함:

Each child in a list should have a unique "key" prop.

왜 위험하냐면

  • 다음 렌더링에서
  • 누가 유지되고, 누가 바뀌었는지 모름


3.2 index를 key로 쓰는 경우 (문제의 시작)

{list.map((item, index) =>
  <li key={index}>{item}</li>
)}

처음상태:

index 0 → haha
index 1 → hoho

❗ 중간에 항목 추가 (kiki 추가 (맨 앞)

index 0 → kiki
index 1 → haha
index 2 → hoho

React의 착각

  • React는 이렇게 생각함:

“index 0은 그대로네? 그럼 기존 DOM 재사용!”


3.3 index key가 괜찮은 경우 (틱택토)

<li key={move}>...</li>

왜 여기선 OK?

  • move는:
    • history index
    • 절대 중간 삽입/삭제 없음
    • 항상 0 → 1 → 2 순서


3.4 key 중복 (가장 위험)

<li key="haha">haha</li>
<li key="haha">haha</li>

React의 반응 (“이름표가 같은 애가 두 명인데요…?”)

Encountered two children with the same key

🧠 React의 사고방식 한 문장

“key가 같으면 같은 컴포넌트다”

그래서:

  • key가 바뀌면 → 새 컴포넌트
  • key가 같으면 → 기존 컴포넌트 재사용



4. stepNumber로 현재를 선택하고, history로 시간을 관리하는 시간여행 로직

4.1 전체 흐름 요약

1. 과거로 이동 (jumpTo)
2. stepNumber 기준으로 history 자르기
3. 현재 상태 복사
4. 새 수 두기
5. 새 history 만들기
6. stepNumber를 새 위치로 이동

이를 함수형 컴포넌트로 바꾸면 다음과 같습니다.


(1) 과거로 이동 (jumpTo)

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

(2) stepNumber 기준으로 history 자르기

const newHistory = history.slice(0, stepNumber + 1);

(3) 현재 상태 복사

const current = newHistory[newHistory.length - 1];
const newSquares = current.squares.slice();

(4) 새 수 두기

newSquares[i] = xIsNext ? 'X' : 'O';

(5) 새 history 만들기

setHistory([...newHistory, { squares: newSquares }]);

(6) stepNumber를 새 위치로 이동

setStepNumber(newHistory.length);