개요
데브코스 파이널 프로젝트인 산책로 기록, 공유 서비스 마실가실 개발을 진행하면서, 산책을 기록하고 저장하는 LogRecord 기능을 팀원 한 분과 함께 작업하였다. 컴포넌트의 디자인 패턴으로는 MVC 패턴을 활용하였는데, 프로젝트 기획 단계에서 다양한 패턴을 경험해보자는 취지로 이를 채택하였고 앞선 2주 간의 스프린트 기간 동안 해당 기능에 대한 기본적인 구현을 끝내놓은 상태이다.
다음 스프린트를 시작하기 전, MVC 패턴으로 구성한 코드에 개선이 필요하다고 생각되어 진행하였던 리팩토링 과정을 정리해놓으려 한다.
기존의 컴포넌트 구조
먼저 기존에 설계한 컴포넌트 구조를 도식화해보았다.
- 상위 컴포넌트인 logRecord는 Model, Controller, View로 구성된다.
- Model에는 하위 컴포넌트들에 공통적으로 사용되는 상태가 존재하며, 커스텀 훅 방식으로 Controller에게 전달하고 있다.
- Controller에는 Model의 데이터를 변경하는 로직들이 존재하며, JSX 방식의 View를 Return하며 특정 이벤트에 의해 호출된다.
- View에서 하위 컴포넌트들을 선택적으로 렌더링한다.
- 하위 컴포넌트는 Standby - Recording - Edit 총 3STEP으로 구성되어 있다.
- Standby 단계에서는 산책 기록 전 맵을 렌더링하고, 버튼 이벤트를 통해 산책을 시작할 수 있다.
- Recording 단계에서는 인터벌하게 GPS를 불러와 사용자의 산책 기록을 맵에 그려주고, 시간을 측정한다.
- Edit 단계에서는 사용자가 산책을 완료한 후 해당 산책에 대한 메모나 핀포인트를 편집할 수 있다. 버튼 이벤트를 통해 최종적으로 산책을 제출할 수 있다.
- 일반적으로 상위 컴포넌트와 동일한 MVC 형태를 띄고 있지만, State와 Setter를 프롭으로 전달받기 때문에 Model은 존재하지 않는다.
인식한 문제
- 컴포넌트 혹은 Model 간의 Prop Drilling이 심함
- Controller가 모든 로직들을 담고 있어 복잡하고 무거움
- 동일한 깊이의 컴포넌트를 변화시키기 위해서는 상위 컴포넌트를 거쳐야 함
한 마디로, 코드를 보기에도 수정하기에도 너무 불편했다.
해결방안 모색
문제들을 해결하기 위해 생각했던 방안은 다음과 같다.
- 여러 STEP에서 재사용되는 상태와 로직은 ContextAPI로 분리하고, 최상위 컴포넌트인 LogRecord에서 Provider를 내려주자
- 프로젝트 내부적으로는 클라이언트 단의 전역 상태를 위해 Zustand를 사용하고 있었지만, LogRecord 하위 영역에서만 상태를 사용하기에 전역적인 관리가 필요없다고 판단되어 ContextAPI로 범위를 축소해주었다.
- 특정 SETP에서만 사용되는 로직들은 동일한 깊이의 Model에서 커스텀 훅 형태로 관리하고, 프롭으로 전달하자
- 외부 컴포넌트에서도 사용될 수 있는 로직들은 전역 유틸 함수 혹은 훅으로 분리하자
- 다양한 형태의 action을 통해 하나의 상태인 logData를 변경시키는 로직들의 경우 useReducer을 활용하자
- Context에서 reducer를 호출 해 원하는 형태로 가공해 사용하도록 하였다.
문제를 해결하기 위한 방안을 정리하고 봤더니, 결국 개선하고자 하는 방향성이 FLUX 패턴 구조와 닮아 있다는 것을 알 수 있었다. 이를 통해 모든 Controller가 사라지고, Prop Drilling이 감소해 코드의 흐름이 간결해진다고 생각하였다.
피드백
개선 방법을 고민하며 문서를 뒤지던 중, 작년 말 데브코스에서 클린코드 특강을 진행해주신 장현석 멘토님의 특강 메모를 발견했다. (정말 우연히..) 우리가 하고 있던 고민과 딱 맞아떨어지는 키워드라 생각되어, 조언을 구하고자 연락을 드리게 되었다.
답변주신 내용을 정리해보자면,
- 강의 간 언급한 Conroller의 제거는 순수 자바스크립트를 기준으로 말한 것이며, 리액트의 컴포넌트 개념을 기준으로 생각해야 한다. (레퍼런스도 함께 보내주셨다.)
- ContextAPI를 이용해 Prop의 Drilling을 줄일 수 있다. 또한 부모 컴포넌트를 생략하고 상태를 가져올 수 있게 되므로 문제 3에 대한 해결 또한 가능해진다.
- 기존에 사용하던 Controller가 View(JSX)를 반환하는 방식은 올바른 Controller의 역할이 아니며, 컨테이너 컴포넌트 패턴에 가깝다.
- Controller 또한 커스텀훅 형태로 구조를 개선하였고, Prop Drilling을 줄일 수 있었다.
- 하위 컴포넌트는 도메인을 담지 않은 채로 분리하는 것이 가장 좋다. 정말 인터페이스적인 요소 단위로 분리해보는 것을 권장한다.
모색한 해결방안과 피드백 받은 내용을 참고하여, 팀원 분과의 페어프로그래밍을 통해 기존 코드를 개선해보았다.
(다음 글에서 계속..)