bass

컴포넌트를 분리하는 이유

하나의 파일만 있어도 프로그램은 돌아가고, 하나의 HTML 파일만 있어도 충분히 사용자에게 성능 좋은 웹을 제공할 수 있습니다.

그런데 왜 컴포넌트라는 개념이 생기고 분리에 힘을 쓸까요?

 

결국 코드는 나 혼자만 보는 것이 아니라 동료 또는 미래의 나와 공유해야 하기 때문에, 가독성이 좋고 유지보수가 용이해야 합니다.

그리고 잘 분리되어 있어야 가독성이 좋고 유지보수가 용이합니다.

나만의 분리 기준과 대처법을 정리해보자.

절대 은탄환은 없고 기준이 없어도 되지만, 제가 경험해본 상황과 대처를 정리하여 기준을 정리해보기로 했습니다.

기준을 정리해서 빠르고 여유있게 그리고 일관성 있게 개발하기를 기대합니다.

그리고 누구나 이해되는 코드를 작성할 수 있기를 바랍니다.

분리 기준 2가지

1. 중복을 발견했을 때 분리

2. 중복은 없지만, 관리 단위가 너무 커서 분리

 

당연한 소리이기도 하지만.. 보통 위의 두가지 상황에서 분리가 꼭 필요합니다.

 

반대로 이런 기준에 부합하지 않는데도 분리를 했더니 오히려 복잡해졌습니다.

그래서 분리할 것이 아니라면, 최대한 가까이 두는 co-location을 적용합니다.

 

어쨌든 이런 기준으로 컴포넌트나 로직을 분리 하다보면, 다음과 같은 3가지 상황이 발생하곤 했습니다.

첫번 째 상황: 1개의 모델, 1개의 기능, 1개의 UI

컴포넌트를 분리했을 때 모델 + 기능 + UI가 1:1로 묶이는 것은 가장 이상적인 상황이었습니다. 이들 중 어느 하나가 변경되었을 때에도 대응하기가 용이했기 때문입니다.

여러 관심사가 모여있는 하나의 커다란 UI가 있다면, 모델 + 기능 + UI가 1:1로 묶이는 영역을 찾아서 컴포넌트로 분리했습니다.

 

여기서,

'UI 구성에 필요한 데이터 구조'가 모델,

'이벤트가 발생했을 때 동작'이 기능,

'이벤트를 발생시키는 HTML + CSS 구조'를 UI라고 생각했습니다.

 

이들을 하나의 생명주기로 가져가는 것이 유지보수에 유리하다고 생각합니다.

 

(그래서 저는 React 라이브러리를 사용하는 이유 중 하나가 DOM API가 느리다던지 jquery에 기능이 부족해서라기 보다는, 모델 + 기능 + 뷰를 동일한 생명주기 안에서 관리하기에 편하기 때문이라고 느꼈습니다.)

두번 째 상황: 1개의 모델, 1개의 기능, 그러나 여러개의 UI

개발하다 보면 아주 흔한 상황입니다. 특히 범용적인 기능일수록 다양한 UI로 표현되고는 했습니다.

처음엔 아니다가도 이후에 다른 모습의 컴포넌트가 생겼습니다.

 

'선택'기능을 예시로 들었을 때,

 

'선택'을 위한 UI는 무궁무진 합니다.

샐랙터? 라디오? 체크박스? 버튼모양? 무엇이든 가능합니다.

 

하지만 '선택'이라는 기능은 아래처럼 중복됩니다.

"여러개의 옵션 중 하나를 선택할 수 있고, 선택이 바뀔 때 리렌더링 한다."

 

이럴 때, 'UI'를 기준으로 컴포넌트를 분리하면 '기능'이라는 횡단 관심사에서 중복이 발생합니다.

 

그래서 중복된 기능과 UI파일을 분리하는 것이 좋았습니다.

기능은 하나의 파일로 분리하고, 다양한 뷰가 해당 기능을 사용할 수 있도록 만드는 것입니다.

 

자세한 구현 방법은 커스텀 훅과, 컴포넌트 합성이 있습니다.

커스텀 훅

뷰에 관여하는 데이터는 상태로 관리합니다. 그리고 이 상태와 관련있는 기능을 묶어서 커스텀 훅으로 분리합니다.

 

뷰를 표현하는 컴포넌트는 범용적인 props를 추가하여 제어 컴포넌트로 만듭니다.

예를 들어 '선택'을 위한 체크박스를 만든다면, `onSelect`와 같이 범용인 props를 새로 뚫어주는 것입니다.

 

이렇게 기능과 뷰를 분리한 다음, 사용처에서는 이 둘을 조합하여 사용할 수 있습니다.

컴포넌트 합성

합성을 활용할 수 있습니다.

커스텀 훅과 다른 점은, 기능과 데이터를 wrapper 컴포넌트에 작성한다는 점입니다.

 

(컴포넌트 합성 구현과 장단점은 글이 길어져서 따로 작성하겠습니다.)

세번 째 상황: 여러개의 모델, 여러개의 기능, 그런데 1개의 UI

'동일한 UI'와 '다양한 기능'을 하나의 파일로 만드는 것은 복잡합니다.

UI 중복 제거에 집중하여 하나의 컴포넌트로 만들다 보면, 컴포넌트의 역할이 너무 커지기 쉽습니다.

 

예를 들어, 하나의 검색 컴포넌트가 사전, 게시글, 등록 페이지에서 사용된다고 가정합시다.

검색을 했을 때, 사전 페이지에서는 결과 페이지로 이동하지만 게시글 페이지에서는 결과를 모달로 보여주고 필터링을 적용해야 합니다.

UI는 동일한데 사용처에서 기능이 조금씩 다른 상황입니다.

 

이처럼 컴포넌트를 사용하는 곳이 많아지면 서로 다른 사용처에서 기능이 조금씩 달라질 가능성이 높습니다.

어떻게 하면 이 복잡한 컴포넌트의 가독성을 높일 수 있을까요?

중복 제거보다는 분리에 집중하기

비슷해 보이는 UI일지라도, 도메인 모델/기능이 다르다면 각각 사용처에 맞도록 다른 파일로 분리합니다.

물론 이 과정에서 DRY원칙에 위배되는 '중복구현'이 UI에서 잠시 발생하기도 합니다.

 

이때 도메인에 의존할 수 밖에 없다면 그대로 네이밍에 표현합니다.

예를들어 검색창 컴포넌트를 분리한다면 사전 검색창, 게시글 검색창으로 나눌 수 있습니다.

도메인에 의존하지 않는 부분 찾기

분리된 여러개의 파일에서 '도메인에 의존하지 않는 뷰'만 추출합니다.

도메인에 아예 의존하지 않는 완전 범용적인 뷰를 의미하는데요, 처음엔 이게 잘 보이지 않다가도 우선 분리하고 나서 보니 잘 보이기 시작했습니다.

 

예를 들면 검색창에서 인풋, 드롭다운, 검색 버튼 처럼 도메인과는 관련 없는 컴포넌트를 분리할 수 있습니다.

 

결과적으로, 파일의 개수는 도메인 파일 + 범용 파일로 많이 늘어났습니다.

하지만 도메인에 의존한다면 그대로 드러내고 분리함으로서 변경에 유연할 수 있도록 하고, 도메인에 의존하지 않는 부분은 중복을 제거하면서 분리할 수 있습니다.

정리

이 글의 포인트는 UI 중복 제거에 '무조건적인 강박'을 갖지 않는 것입니다. (사실 제가 중복을 정말 싫어합니다.)

개인적으로 기능에서의 중복은 허용하기 쉽지만, 뷰에서의 중복을 허용하기란 쉽지 않았습니다.

물론 중복구현이 안 좋다는 것은 알고있지만, 그렇다고 분리의 이점을 잃을 수는 없습니다.

 

중복 제거와 분리의 trade-off를 잘 판단해야 하며, 때로는 '뷰의 중복 제거'보다는 '도메인의 과감한 분리'를 선택할 수 있는 용기와 관점이 필요하지 않을까 생각됩니다. (특히 '여러개의 모델, 여러개의 기능, 그런데 1개의 UI'일 때)

그리고 여러 곳에서 사용되는 컴포넌트를 설계할 때, 범용적인 영역과 의존해야하는 영역을 또한번 잘 고민 해야겠습니다.

 

정답은 없지만 계속 고민해보겠습니다. 읽어주셔서 감사합니다!

 

profile

bass

@bassyu

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!