🦈 react

[React로 테트리스] 컴포넌트 구조 설계

읏차 2023. 12. 18. 14:08

컴포넌트 계층 구조에서 useState를 통한 상태변경 과정을 이해했다..라고 생각하고 react로 작은 프로젝트를 만들어보기로 했다. 테트리스로 정했고 1차적으로 시간 변화에 따라서 내 블록이 이동하는 것까지 구현하기로 했다.

일단 js파일 하나에서 구현했고 다행히..? 금방 완성됬다.

tetris.mp4
1.00MB

 

현재 한 파일에 모든 코드가 다 들어가 있어서 별 기능도 없는데도 벌써부터 코드에서 구린내가 나기 시작해서 컴포넌트 별로 분리하고 로직과 렌더링 부분을 분리하기로 마음먹었다. 찾아보니 로직부분은 hook을 이용한다고 한다. hook은 쉽게 말하면 리액트의 함수를 한번 감싸서 내 상황에 맞게 커스터마이징해서 사용한다고 생각하면 된다.

 

여기서 구조 잡는게 정말 쉽지 않았다.. 이것만 한건 아니지만 거의 반 이상을 투자했는데 4일 정도 걸린거 같다. 마지막날은 새벽 5시까지 했다.. 너무 화나서.. 그래도 완벽하진 않겠지만 나름 논리적으로 한 것 같다.

간단히 설명하면 컴포넌트 계층 구조는 Ingame > board > cellGrid > cell

 

Component

  • Ingame : timer(hook)정보를 가져옴. react의 context를 사용해 시간상태를 하위 컴포넌트에 provider해줌. 
  • board : cellGrid의 정보. 각 셀의 상태를 useState()를 통해 실제로 update 해줌
  • cellGrid : 시간마다 변경되는 player(hook) 정보를 가져와서 기존 cellGrid의 cell정보와 비교하여 상태변경이 필요하면 board에 해당 cell상태변경 요청
  • cell : 상위컴포넌트에서 받아온 상태에 따라 화면에 cell을 그려줌

Hook

  • UseTimer : 내부에서 useState를 사용하고 있으며, 게임 내 시간상태를 업데이트 함
  • UsePlayer : 시간상태를 useContext로 가져와서 시간변화에 따른 player정보를 업데이트 해줌

 

 

어려웠던 점, 헷갈렸던 점

  • React, Component, State
    component를 class처럼 생각해서 class에는 변수들이 있고, component는 state가 있고 useState를 사용해야지??? 이러면서 component마다 useState를 사용하면서 말도 안되는 코드가 완성됬다... 개념이 계속 섞이면서 이게 맞나? 하면서 구조를 짜고 안되겠다 싶어서 다시 함수 하나하나에 대해 공부했다
    • React와 component, state에 관해 내가 다시 정의하자면 component는 웹페이지를 구성하는 요소들을 지칭하며 그 중 state를 관리하는 component가 존재한다. 그리고 state를 관리하기 위해서 react라는 라이브러리를 사용한다. 이렇게 생각하고 구조를 만드니 나름 논리적으로 작성이 됬다.
  • Hook
    위에서 component는 화면을 구성하는 요소들이라고 정의했다. 그러면 hook은 뭘까? 이것도 나름 정의해보면 실제 화면에 그려지지 않지만 react의 state를 관리해주거나 state로 logic을 처리하는 프로그래밍 기법이라고 하겠다. 이 hook들은 내부적으로 react의 useState 등을 사용해서 동작하고, 이런 custom hook을 naming할때도 u를 대문자로 하여 UsePlayer 이런식으로 이름을 짓는다고 한다.

 

  • UseRef
    테트리스는 시간이 지날때마다 내 블록이 아래로 떨어지게 된다. timer가 필요하다. timer를 구현을 위해 검색을 해보니 useRef와 useEffect를 통해 생성될때 타이머 시작하고 타이머 동작은 useRef의 current로 관리하며, 삭제될때 interval 함수를 clear해주고 있었다. useRef와 useState의 차이점은 해당 component가 재실행된다는 것으로 useRef를 사용하면 값만 변하고 component는 재실행 되지 않는다. 그래서 useRef를 통해 timer를 구현했는데 timer가 component안에 있는게 좀 어색했다. hook으로 빼면 훨씬 깔끔하고 추후 관리 측면에도 좋아보여서 timer를 hook으로 만들어서 처리했는데 갑자기 timer가 동작하지 않았다.
    이유는 바로.. useRef 때문이였다. 해당 hook이 다시 실행되지 않아서 UseTimer()를 사용한 component가 시간이 변할때마다 update가 되어야하는데 useRef때문에 timer내부에서 시간값은 변경이 되는데 return이 되지않아서 component가 아무런 동작도 하지 않았던 것이다.

 

  • UseContext
    timer값을 useContext로 처리했다. useContext는 여러 하위 component에서 state를 참조하기 위해 상위 component로 부터 props를 설정해서 처리해줘야 하는데 component의 depth가 깊어지다보면 중간에 상관없는 component에도 props로 값을 넘겨줘야 하기때문에 이럴때 useContext를 사용하면 해당 props를 사용하지 않는 component는 그대로 두고 상위 component에서 provider로 값을 설정해주고 실제 props를 사용할 하위 component에서 useContext를 통해 provider한 값을 가져올 수 있게 된다. 
    그런데, 여기 프로젝트에서 보면 UseTimer로 업데이트되는 값을 Ingame component(상위)에서 provider하고 있고.. 하위 component가 아닌.. UsePlayer라는 hook에서 useContext로 시간값을 가져왔는데 시간이 변할때 마다 UsePlayer가 업데이트 되고있다. 하위 component가 아닌데 왜 잘 가져와 지는걸까 보면 하위 component인 cellGrid에서 UsePlayer를 사용한다. 이러면 custom hook이라도 하위 component처럼 처리되는 걸로 보인다.
    실제 그런지 cellGrid component에서 UsePlayer hook을 사용한 부분을 주석처리하니 UsePlayer에서 더 이상 시간값에 따라 업데이트가 일어나지 않았다.

 

  • UseEffect
    여기까지 하니 잘 동작할거 같다. 실행해보니 시작과 동시에 블럭이 바닥을 찍는다. UsePlayer는 시간값이 변할때마다 업데이트 되며 그때마다 y축으로 한칸씩 이동하도록 구현했는데 useEffect에 대한 처리를 전혀 하지 않았다. useEffect를 추가하여 dependencylist에 시간값을 추가했고 추가로 UsePlayer를 사용하는 CellGrid에서도 player위치에 따라 setState함수가 업데이트 되도록 useEffect를 넣어줬다.

 

잘된다. 정리하고 보니 결국 하나도 이해하지 못한 상태에서 시작한 꼴이였다. 처음 react를 시작하시는 분들은 일단 useState와 useEffect를 완벽하게 이해하고 그 이후에 뭐라도 시작하길 바란다. 안그럼 고생한다.