당신의 컴포넌트는 안녕하신가요?
조금 관심을 끌기 위한 멘트로 글을 시작했는데, 컴포넌트가 잘 지내는지 헬스 체크를 해보자는 의미였다. 잘 안 지낼 수도 있으므로... 🤣 아무튼, 이를 확인하기 위해 컴포넌트가 무엇인지부터 알아보도록 하자.
리액트 공식 홈페이지에서는 컴포넌트를 다음과 같이 표현하고 있다.
Components let you split the UI into independent, reusable pieces, and think about each piece in isolation.
(컴포넌트는 UI를 독립적이고, 재사용 가능한 부분으로 분리해주며 각각을 고립시켜 생각할 수 있게 해줍니다.)
'독립적', '재사용 가능하도록 분리', '고립시켜 생각'이라는 말들이 나오는데 우리가 만드는 컴포넌트가 완벽히 이러한 특징을 가지느냐고 자신에게 반문해보면 아마 아닐 것이다.
사실 위의 설명은 컴포넌트의 최대 목표를 의미하는 말이 아닐까 생각한다. 이러한 목표를 완벽하게 지키면 가장 좋겠지만, 개발하면서 위의 특징들을 못 지키는 사태가 빈번하다. 그래서 나는 이러한 특징들을 어느 정도 지키고 있는지 확인하는 것을 컴포넌트의 헬스 체크라고 명명하고, 주기적으로 이러한 헬스 체크가 이루어져야 한다고 생각한다.
그렇다면 헬스 체크에서 가장 중요한 개념은 무엇일까? 바로 의존성
이다. 의존성을 잘 관리한 컴포넌트를 만들게 되면 유지보수 효율이 굉장히 올라가는데, 오늘은 컴포넌트 의존성에 대해서 얘기해볼 생각이다.
의존성을 네이버 국어사전에 검색해보면 다음과 같이 설명하고 있다.
다른 것에 의지하여 생활하거나 존재하는 성질.
개발에서는 어떨까? 개발에서의 의존성이란 어떤 것과 다른 것이 변경에 있어서 서로 영향을 주고받는 관계
를 의미한다.
예시로 자전거 조립 회사와 자전거 안장 제조 회사를 생각해보면 좋을 것 같다.
조립 회사가 안장 제조 회사에서 만드는 A 안장을 사용하고 있다고 가정해보자.
당연히 조립 회사는 안장 제조 회사에 의존된다. 안장 제조 회사에서 A 안장의 소재를 바꾸기라도 한다면 당연하게도 조립된 자전거의 안장도 소재가 변경될 것이다.
그렇다면 안장 제조 회사는 조립 회사에 의존되지 않을까? 아니다, 의존된다.
예를 들어 조립 회사가 안장 제조 회사에 A 안장의 소재를 바꾸어 A` 안장으로 바꾸어 달라는 요청을 한다.
당연히 될 줄 알았건만, 안장 제조 회사에서는 거절을 한다.
A 안장이 다른 조립 회사에서도 쓰고 있어서 그건 좀 곤란할 것 같아요 😥
그렇다. 우린 망했다. 안장 제조 회사도 조립 회사에 의존되어 함부로 수정할 수 없는 처지가 된 것이다. 이처럼 의존성이란 한 방향으로 이루어지는 것이 아닌, 양방향으로 영향을 주고받는 관계
를 의미한다.
영향, 변경, 수정과 같은 얘기를 했는데, 약오르게 무언가 감이 잡힐 듯 말듯 한다. 🤔
의존성 관리가 중요한 이유는, 의존성 관리가 잘 되었을 때 예상치 못한 변경에 강하기 때문에 유지보수 효율이 굉장히 올라가기 때문이다.
혹시 유저 정보에 블로그 주소라는 데이터가 하나 추가되었을 뿐인데 매우 많은 컴포넌트를 수정해야 하는 경우, 또는 해당 변경 사항이 생겼을 때 어떤 컴포넌트를 수정해야 하는지 분명하게 알 수 없는 경우가 있었는지 한 번 생각해보자. 그것이 바로 의존성 관리를 해야할 때라는 신호이다.
그렇다면 의존성 관리는 어떻게 하는 걸까? 정답은 없지만, 의존성 관리의 가장 큰 줄기는 불필요한 의존성을 제거하거나 약화시키고, 불가피하게 강하게 의존되는 요소들은 가깝게 두는 것에 있다.
아니 뜬구름 잡지 말고 좀 더 자세히 알려주세요 ㅠ 😭
다행히도 선배 개발자분들이 많은 고민을 하셨고 우리에게 인사이트를 주고 있다. 같이 따라가 보자.
(출처 - 컴포넌트, 다시 생각하기)
컴포넌트가 의존하는 것은 대체로 다음과 같다.
1. 스타일
css-in-js의 경우 import하여 가져온 styles.ts 파일이라고 할 수 있다.
2. 로직
custom hook들을 의미한다.
3. 전역 상태
redux, react-query, recoil 등이 여기에 해당한다.
4. props(상위 컴포넌트 의존성)
props도 변경되면 상위 컴포넌트도 같이 변경되기 때문에 서로 의존한다고 할 수 있다.
5. 👑 서버 데이터 스키마
가장 중요하다고 생각한다. 전역 상태, 로직, 스타일 등의 변경은 많은 경우 서버 데이터 스키마의 변경에서부터 시작된다. 게시물 내용에 좋아요 갯수가 추가된다든가 하는 경우가 있다.
이제 앞에서 살펴본 컴포넌트의 복잡한 의존성을 잘 관리할 수 있는 4개의 원칙에 대해서 알아보자.
여기 소개된 4개의 원칙이 항상 정답은 아니므로 더 개선하기 위해서는 어떻게 할 수 있을지 열린 마음으로 바라보면 좋을 것 같다.
스스로 드는 회초리가 가장 값진 법.
필자가 개발에 참여한 놀토 - 놀러오세요 토이프로젝트라는 플랫폼의 나쁜 예시를 알아보고 개선하면서 원칙을 소개해보도록 하겠다.
비슷한 관심사, 즉 깊은 의존성을 지닌 것들끼리는 한 폴더에 같이 두는 것이 좋다. 그래야 어떤 컴포넌트를 수정할 때, 컴포넌트가 의존하는 파일을 찾느라 고생하지 않아도 된다. 집중력이 분산되지 않고, 효율적으로 컴포넌트를 수정할 수 있다.
👎 나쁜 예시
OAuth 로그인을 처리하는 페이지 컴포넌트다. useOAuthLogin이라는 커스텀 훅은 OAuth에서만 쓰이고 있지만, 해당 훅 파일은 hooks 폴더에 따로 떨어져 있다.
👍 개선해보기
의존성이 깊은 파일끼리 모아주기 위해 얼른 OAuth 폴더로 넣어주자.
클라이언트에서 데이터를 Id를 통해 전역적으로 관리하면 큰 이점이 있다. 바로 상위 컴포넌트에서 해당 데이터의 스키마를 알 필요가 없고, Id만 내려주면 하위 컴포넌트가 원하는 데이터에 바로 접근할 수 있다는 점이다. 이렇게 되면 상위 컴포넌트는 데이터 스키마에 의존하지 않기 때문에, 데이터 스키마의 변경이 일어나도 상위 컴포넌트를 수정할 필요가 없다
👎 나쁜 예시
예시로 인기 있는 토이 프로젝트를 나타내는 HotFeedsContent와 LargeFeedCard 컴포넌트를 확인해보자.
우선 컴포넌트를 모델 기준으로 나눈 것이 아니라 현재 UI 형태를 기준으로 컴포넌트를 나눈 것부터 문제다. Large라는 모호한 네이밍으로 컴포넌트를 만들었기 때문에 해당 컴포넌트에서 데이터를 가져오기에 애매해졌다. 그래서 상위 컴포넌트에서 feed를 prop으로 받아오고 있다. 이렇게 되면 상위 컴포넌트인 HotFeedsContent가 feed 데이터 스키마를 알고 있어야 하는 것이므로 하위 컴포넌트와의 의존성이 강해진다.
👍 개선해보기
LargeFeedCard -> HotFeedCard로 컴포넌트 네임을 수정하기
컴포넌트를 모델 관점에서 바라보기 위해서 네임을 수정
HotFeedCard의 prop으로 feedId를 받아오도록 수정
기존에는 feed를 그대로 상위 컴포넌트에서 주입하고 있었기 때문에 feed 데이터 스키마가 변경되면 상위 컴포넌트도 같이 변경해야 했다.
feedId를 prop으로 받도록 수정하여 의존성을 느슨하게 만들어주었다. useData
는 전역 상태 저장소에 접근할 수 있는 훅으로 가정했다.
의존성을 prop 구조에 그대로 드러내면 컴포넌트 의존 구조가 명확히 보이고 어떻게 의존성을 느슨하게 만들어야 하는지 눈에 보인다.
그래서 의존 관계를 prop 구조나 이름에 그대로 드러내는 것이 좋다.
👎 나쁜 예시
토이 프로젝트를 나타내는 RegularFeedCard 컴포넌트를 확인해보자.
author 같은 경우는 의존 구조가 명확하지만 thumbnailUrl은 다른 목적의 모델임에도 의존 관계가 표현되지 않고 있다.
👍 개선해보기
feed와 thumbnail의 의존 구조를 명확하게 표현해주기
thumbnail도 따로 의존 관계를 prop에 표현해 주었다.
컴포넌트가 2개 이상의 모델에 의존하고 있는 것이 보인다면 Id 기반으로 정리하여 의존성을 약화시키자
thumbnail의 의존 관계를 prop 구조에 표현해주니 RegularFeedCard 컴포넌트가 feed 외에도 thumbnail, author 등, 2개 이상의 모델에 의존되고 있다는 것이 보인다.
하나의 모델에만 의존되게 하기 위해 2번 원칙인 Id 기반으로 정리하고, 각 컴포넌트에서 필요한 데이터 스키마를 컴포넌트 내부에서 정의해준다.
이렇게 만들면 RegularFeedCard와 Thumbnail은 서로 데이터 스키마에 대해 알 필요가 없으므로 의존 관계를 느슨하게 만들 수 있다.
개발할 때 편리한 것보다 변경할 때 편리하기 위해서의 관점으로 컴포넌트를 나누는 것이 좋다. 보통 모델(데이터의 목적)에 따라 변경이 자주 이루어지므로 모델이 같다면 재사용하고, 다르다면 컴포넌트를 분리하도록 하자. 모델이 다르지만 현재 UI가 같다고 컴포넌트를 재사용하게 되면, 추후 모델에 따라 다른 UI 변경점이 생길 때 곤란해진다.
👎 나쁜 예시
진행 중인 프로젝트와 완성된 프로젝트의 UI가 같다는 이유 때문에, RegularFeedCard 컴포넌트로 재사용되고 있다. 하지만 진행중 프로젝트와 완성된 프로젝트의 모델은 다르다. 현재는 UI가 같지만, 모델이 다르기 때문에 충분히 다른 방식으로 변화할 가능성이 있다. 예를 들어 완성된 프로젝트의 모델은 배포 url을 가지기 때문에 나중에는 서비스로 이동하기
라는 버튼이 완성된 프로젝트 카드에만 추가될 수도 있을 것이다.
👍 개선해보기
RegularFeedCard를 ProgressFeedCard, CompleteFeedCard 2개의 컴포넌트로 분리한다.
이렇게 분리하게 되면 모델에 따라 카드의 형태가 달라진다 해도 쉽게 변경할 수 있을 것이다.
사실 4개의 원칙보다도 본인의 경험에서 불편함을 찾는 자세가 굉장히 중요하다고 생각한다. 4개의 원칙을 완벽하게 적용해도 분명히 어딘가 유지 보수하기 불편한 점이 있을 것이다. 거기서 스스로 개선까지 할 수 있으면 금상첨화지만, 불편함을 찾는 것만으로도 성장할 준비가 되어있다는 의미이기 때문에 괜찮은 경험이라고 생각한다. 그 상태에서 누군가 조금 이끌어주는 것만으로도 크게 성장할 수 있을 것이다.
나도 컴포넌트 기반으로 개발을 하면서 간혹 파일을 찾기 위해서 폴더를 굉장히 많이 뛰어넘는다든가, 하나의 수정 사항이 생길 때 여러 컴포넌트를 수정해야 하는 등 불편함을 많이 느끼고 있었는데 마침 좋은 영상을 접하게되어 큰 인사이트를 얻을 수 있었다.
앞으로는 컴포넌트 개발을 하면서 컴포넌트 의존성을 도식화하는 습관을 지녀야겠다. 도식화하여 어떤 부분에서 의존성이 강한지 한눈에 파악하고, 어떻게 더 나은 방법으로 개선할 수 있을지 고민하는 사람이 되고 싶다.
좋은 글 감사합니다 :)