세팅 및 클릭 이벤트는 1편 참조!
https://velog.io/@dongha1992/react-tic-tac-toe
Board에서 state를 관리할 때 배열을 복사했다. 불변성을 지키기 위해서다.
배열, 객체 수정하기
let player = {score: 1, name: 'Jeff'};
player.score = 2;
let player = {score: 1, name: 'Jeff'};
let newPlayer = Object.assign({}, player, {score: 2});
//혹은
let newPlayer = {...player, score: 2};
그럼 불변성이 가진 이점을 알아보장!
- 특정 행동을 취소하고 다시 실행하는 기능에 적합하다.
- 변화를 감지한다.
- React가 리랜더링 시기를 결정한다.
Square를 함수 컴포넌트로 바꾼다! 왜냐면 state가 필요없기 때문이다. props를 입력받아서 렌더링할 대상을 반환하는 함수를 작성할 수 있다.
funtion import/export
요렇게 함수일 때는 export를 function 앞에 써준다. 그리고 import 할 때는
저것만 따서 온다!
이제 다음 순서에서 "O"가 나오도록 구현해야 한다. Board에서 초기 state를 설정하면 된다.
boolean을 이용해 클릭할 때마다 순서를 바뀌게 한다.
아름답다.. 오늘 여러 번 아름다운 걸 보았다.
Board 안에 render 함수 안에 있는 status 값도 바꿔준다.
자바스크립트에서 짰던 로직과 비슷하다. winningArray를 정해두고 그 배열에 해당하는 index에 같은 표시가 되어있는지 확인하면 된다.
일단 lines에 for문으로 [a,b,c]를 각 lines에 배열들에 distructiong한다. 그리고 square[a]가 있고 그게 square[b], square[c]와 모두 같다면 square[a]를 리턴하고 기본적으로는 null을 리턴하는데 이 부분이 좀 헷갈린다.(사실 엄청 많이) 그런데 방금 쓰면서 깨달았다 그래야 누가 이겼는지 아니까!!!!!!!! 유레카!!!
그냥 함수를 작성하는데 export 문 아래에 작성했는데도 작동이 된다. 인자로 받은 squares가 어디서 오는지 모르겠다. 이걸 찾아보장. 쓰다 또 생각났는데 혹시 인자는 필요없는 거 아닐까? render에서 이미 this.state.squares를 받아 함수를 실행했으니까!?!?!?
이제 winner가 나왔으면 winner를 알려야 하니까 status를 바꿔야 한다.
쓰면서 입이 쩍 벌어졌다. winner의 결과값으로 "X", "O", null이 나오고 status를 선언만 하고 if(winner)로 하면서 if/ else if를 안 써도 되는 기적을 일으켰다. 무지 아름답다...
그리고 승자가 있거나 표시가 있으면 클릭 작동이 안 되게 하는 것인데 boolean으로 끝냈다. 진짜 말이 안 나온다.
진짜 이건 미친 거 같다. 무르기가 가능하다. "따고 배짱"이 없는 게 국룰이라지만 뒀던 수를 무를 수 있는 기능이 있다. 자바스크립트로는 어떻게 할지 감조차 오지 않는다. 너무 설레서 지금 쓰고 있는 이 글을 닫고 구현를 빨리 하고 싶지만 그 마음을 억누르고 한 글자 한 글자 옮겨본다.
시간 여행이 가능하다는 것은 과거의 액션, 즉 state가 업데이트 되기 전의 state를 기억하고 있다는 뜻이다. 이 때, 불변의 가치를 다시 느낄 수 있다. slice
를 사용해서 매 동작마다 새로운 배열을 만들었고 이를 불변 객체 취급했다. 이를 통해 시간 여행이 가능하다.
일단 과거의 배열을 history라는 내가 진짜 vscode 사상 처음으로 써보는 배열의 이름으로 저장한다. history는 첫 수부터 모든 수를 다 담아둔다. 아래와 같이 저장될 것이다. 어떤 컴포넌트가 history state를 가지고 있을 지 결정해보장
미쳤다..이게 가능하다니...
이전 동작에 대한 리스트를 보여주기 위해서는 최상의 단계인 Game 컴포넌트가 필요하다. 왜 Board가 아니라 Game 컴포넌트일까. 그건 나중에 생각해보자.
Sqaure 컴포넌트에서 Board컴포넌트로 state를 올렸던 것처럼 Game 컴포넌트에 history가 생겼기 때문에 더 이상 Board에서 squares state를 관리하지 않아도 된다.
혁명적이다.. 너무 추상적인데 뭔가 직관적인 거 같기도 하고 어렵다..
최상위 컴포넌트인 Game 컴포넌트에 이 state를 주었다. 이제 Board랑 연결을 어떻게 할까 고민해봐야 한다. state값을 props로 전달해야 하는데 어떻게???? 너무 추상적이야...
먼저 Game 컴포넌트에서 Board 컴포넌트로 squares와 onClick props를 전달한다. Board에서 여러 개의 Square에 쓰일 클릭 핸들러를 가졌기 때문에 각 Square의 위치를 onClick 핸들러에 넘겨주어 어떤 Square를 클릭했는지 표시해야 한다.
이제 Board를 수정해야 한다. 일단 squares[i]는 props로 전달받는다. (board에 state 값이 없고 Game 컴포넌트로부터 props를 전달받아야 한다.) 그리고 handleClick 함수도 props.onClick이 되어야 한다. 이건 나도 잘 모르겠다.. 추측하기론 최종적으로 최상위 부모 컴포넌트만 직전 함수를 호출하기 때문!?
Board에서 renderSquare 부분에서 Square 컴포넌트를 props로 고쳐준다.
Border에 있던 변수들을 모두 Game 컴포넌트로 가져온다. Game이 이제 state를 관리하기 때문!
이제 진짜 어려운 부분인데 handleClick 함수를 Game 컴포넌트로 이동해야 한다. 진짜 왜? 왜 이동해??
Game 컴포넌트의 state 구성이 달라졌기 때문에 handleClick도 수정해야 한다.
state에 history란 배열에 객체가 담긴 것이니까 squares도 알맞게 조정해준다. 그리고 setState에 기존 history에 업데이트된 history에 복사한 squares를 concat한다. 여기서부터는 나의 범위를 넘었기 때문에 감상모드로 돌아간다. 반복해서 하다보면 이해하는 날이 오겠지!
11월 14일 clear!
시작하자마자 null로 가득찼다.
이제 하나를 클릭하면 클릭했던 squares가 담긴다. 현재 새로 추가된 배열이 current다.
플레이어에게 과거로 이동할 수 있는 목록을 보여줄 수 있다. history를 map해서 가능하다고 하는데 일단 지켜보장!
history를 맵하는데 step은 현재 board고 move는 인덱스다.
console은 이렇게 표시된다.
return 값으로 li에 button을 담는다. button에 onClick prop을 주고 onClikc이 실행될 때마다 jumpTo가 실행된다. 이제 jumpTo 함수를 구현하기 전에 state에 stepNumber라는 값을 주고 jumpTo가 실행될 때 인자로 들어온 move(index)의 값이 step 넘버가 된다.
와 진짜 어렵다 진짜 어려워서 머리가 쥐날 거 같은데 기분이 좋아... 리액트 좋아...
시간 이동의 핵심
stepNumber state는 사용자에게 표시되는 이동을 반영한다. 새로운 이동을 만든 후
this.setState
의 인자로stepNumber: history.length
를 추가하여 stepNumber를 업데이트 해야 한다. 이를 통해새로운 이동 후 이동이 그대로 남아 있는 것을 방지한다.
this.state.history를 this.state.history.slice(0, this.state.stepNumber + 1) 하는 역사적인 순간이다.
감격 그 자체... 코딩 열심히 하겠습니다...