이번 포스팅에서는 진행중인 프로젝트의 Page 단위 컴포넌트를 리팩터링 하는 과정과 느낀점을 담아보겠습니다.
도메인이 듬뿍 담긴 컴포넌트의 리팩터링 과정을 담다보니 다소 복잡합니다.
코드를 이해하기 보다는 가볍게 느낌만 봐주세요. (느낌만 봐도 충분히 복잡합니다..ㅎ)
과정과 설명 없이 Page컴포넌트의 Before / After 만 보고 싶으시면 아래 접힌글을 펼쳐서 코드를 확인해 주세요.
맨밑으로 가서 느낀점만 보셔도 됩니다...
Before
After
1. Page 컴포넌트의 문제점
우선 리팩토링 대상 컴포넌트 코드를 살펴봅시다.
미리 주요 관심사들에 대해 주석으로 표시해 두었습니다.
코드를 하나하나 이해하는 것 보다는, 주석 부분만 읽으면서 '관심사가 이렇게 많구나~' 정도만 파악하시면 좋을것 같아요.
위 page 컴포넌트를 보고 제가 느낀 문제점은 다음과 같습니다.
- 수 많은 관심사를 처리하고 있기 때문에 한눈에 파악하기 힘들다.
- 각 하위 컴포넌트로 전달되는 props와 그 역할이 구분되지 않는다.
- 페이지의 DOM 구조 또한 파악되지 않는다.
만약 기획이 변경되어 해당 페이지가 수정되어야 한다면?
개발자는 우선 코드 흐름을 파악하는데 많은 시간을 들여야겠죠.
또한 기존 기능을 유지하며 특정 기능을 수정하기 매우 어려울겁니다.
어디를 어떻게 수정해야 하는지 감을 잡고나면, 이미 많은 시간이 흘렀을 겁니다.
(실제로 저는 코드를 파악하는 데만 꼬박 2일이 걸렸습니다...😅)
2. 관심사 분리 시작
개인적으로는 해당 페이지의 레이아웃을 나타내는 것이 Page 단위 컴포넌트의 주 역할이라고 생각합니다.
나머지 역할을 최대한 분리해 봅시다.
2-1. 투표 관련 컴포넌트를 총괄하는 상위 컴포넌트 생성(컴포넌트 분리)
Page 컴포넌트에 존재하던 투표 관련 컴포넌트를 총괄하는 VoteInterface 컴포넌트를 생성합니다.
이후 투표와 관련된 모든 로직을 옮겨봅니다.
VoteInterface
VoteInterface 컴포넌트에 투표 관련 상태, 핸들러 함수 및 유틸 함수를 모두 옮겼습니다.
(VideoPlayer 관련 로직은 우선 주석처리 했습니다.)
로직이 분리된 Page 컴포넌트를 봅시다.
SongDetailPage
컴포넌트와 로직이 총괄 컴포넌트로 묶여 비교적 가벼워진 모습입니다.
보이지 않던 DOM 구조가 보이기 시작합니다.
2-2. 투표 관련 상태 및 로직 Context로 분리하기 (복잡하게 얽혀있는 상태와 Props 정리)
하지만 여전히 여러개의 상태가 어떻게 사용되는지, 어디서 어디로 흐르는지 알기 어렵습니다.
투표 관련 컴포넌트들이 공통적으로 필요로 하는 상태를 찾아 Context로 분리하겠습니다.
VoteInterfaceContext
Context를 SongDetailPage의 VoteInterface 컴포넌트에 적용해 줍니다.
SongDetailPage
Context를 적용한 VoteInterface 컴포넌트를 봅시다.
VoteInterface
표시된 부분처럼 각 투표 관련 컴포넌트에서 공통으로 요구하던 상태를 Context로 부터 제공받습니다.
과도한 props가 줄어들었습니다.
2-3. VideoPlayer 관련 상태 및 로직 Context로 분리하기
투표와 관련된 분리 외에도 유튜브 API에 의해 제공되는 player 객체를 초기화하고 사용하기 위해서는 상태가 필요했습니다.
(초기화는 Youtube컴포넌트에서 이뤄집니다.)
또한 각 투표 관련 컴포넌트에서는 이 상태를 통해 VideoPlayer를 조작할 수 있어야 했습니다.
이를 위해 VideoPlayer 관련 상태와 로직도 Context로 분리해 보겠습니다.
VideoPlayerContext
Youtube 컴포넌트(player상태가 초기화되는 곳)와의 재사용을 고려하여 투표 도메인과 관련된 로직을 두지 않았습니다.
Context를 SongDetailPage의 Youtube, VoteInterface 컴포넌트에 적용해 줍니다.
SongDetailPage
이때 의도대로 VideoPlayerProvider를 Youtube 컴포넌트와 재사용할 수 있도록
재사용 가능성이 없는 VoteInterfaceProvider 보다 상위에 두었습니다.
이제 Page 컴포넌트에 존재하던 player 반복재생 effect 로직도 분리 가능해졌습니다.
투표 관련 상태들의 변화에 의존하던 effect를 VoteInterfaceContext에 격리해 봅시다.
VoteInterfaceContext
2-4. 유틸함수 분리 및 메세지 관련 로직 정리
마지막으로 Page컴포넌트에 존재하던 클립보드 복사 관련 함수를 유틸로 분리해주고
기획의 변경으로 인해 더이상 사용하지 않는 메세지 관련 함수를 삭제합니다.
코드는 따로 담지 않겠습니다.
3. 관심사 분리 끝😎
이제 관심사가 분리된 각 컴포넌트를 확인해 봅시다.
3-1. SongDetailPage
관심사 분리를 통해 DOM 구조를 파악하기 비교적 쉬워졌으며,
Page 컴포넌트가 어떤 역할을 하는지도 명확하게 알 수 있습니다.
초기 Page 컴포넌트와 비교하면 책임이 상당히 줄어든 것을 확인할 수 있습니다.
api 통신의 결과를 하위 컴포넌트로 전달하는 책임정도만 가지고 있네요.
3-2. VoteInterfaceContext
투표 관련 상태와 핸들러, 이에 영향을 받는 effect 로직까지 Context에 잘 모여 있습니다.
투표 플로우와 관련된 에러가 발생한다고 가정했을 때, 더이상 Page 컴포넌트까지 가지 않아도 됩니다.
Context 선에서 해결할 수 있습니다.
3-2. VideoPlayerContext
자세하게 설명하진 않았지만, YouTube 컴포넌트에서 videoPlayer 상태를 초기화한 후
VideoPlayerContext를 통해 어디서든 videoPlayer를 컨트롤 할 수 있습니다.
전체적으로는 여전히 개선할 부분이 많지만, 기존과 비교하면 정말 많이 나아졌네요!
4. 기능 개발 vs 리팩터링 (feat. 한정된 시간...⌛)
이번 스프린트에서 페이지 단위 리팩토링을 진행하면서, 기술적인 부분을 제외하고도 여러 가지 생각을 하게 됐습니다.
4-1. 기능 개발이 우선인가? 리팩터링이 우선인가?🧐
당연히 무엇 하나 놓치지 않는게 베스트이겠지만 현실적으로 쉽지않죠.
이건 정답이 없는 문제인것을 알지만 조금 오래 고민했던것 같아요.
작업을 진행하면서 '당장 프로젝트에서 개발해야 하는 기능은 많은데, 리팩터링을 하고있는게 맞나?' 생각하기도 했어요.
한편으로는 '주요 기능이 모여있는 컴포넌트인데... 이거 이대로 두면 안되는데...' 생각하기도 했고요.
당장 서비스의 상황만 놓고 보면 우선도가 높지 않았을 수도 있어요.
하지만 머지않은 미래에 해당 코드를 수정해야 하는 개발자는 제 노력만큼의 시간을 아낄 수 있겠죠.
4-2. 의식적인 리팩터링의 중요성
답이 없는 고민을 반복하다가 내린 결론은 코드를 작성하면서, 의식적으로 코드 흐름을 정리해야 한다는 것입니다.
작성한 코드 흐름이 내 머리속에서는 자연스럽다고 생각 하더라도, 잠시 되돌아 본다면 운좋게 어색한 부분을 찾을 수도 있겠죠.
결국 언젠가는 눈덩이가 구르겠지만, 마당이라도 한번 더 쓸어놓으면 혹시 모르지 않을까요...?
대왕 눈덩이가 되는 것은 막을 수 있을지도...🤪
4-3. 마무리
이건 메모장에다가 코드를 파악하면서 적어놓은 내용중 일부입니다.
다음에 해당 코드를 수정하는 팀원은 이와 같은 수고를 덜 수 있었으면 좋겠습니다.
이 모든 과정이 저에게도 큰 경험치가 되었을 거라고 믿습니다.
한정된 시간에서의 리팩토링이 하나의 기능 개발보다 의미 있었기를 바라면서 포스팅 마무리할게요~!
'Frontend > React' 카테고리의 다른 글
react - key props(feat. 공식문서) (8) | 2023.06.14 |
---|---|
react - useRef (0) | 2022.06.14 |
react - public폴더 이미지 절대경로로 사용하는 법 (7) | 2021.08.07 |
react - favicon, 타이틀 변경 (0) | 2021.08.01 |
react - context API (0) | 2021.06.20 |