
우리가 개발을 하며 일상처럼 만나는 단어, 바로 '컴포넌트'입니다.
컴포넌트는 독립적이고 재사용 가능한 소프트웨어 모듈 또는 구성 요소를 의미합니다. 웹사이트 개발에서는 화면을 구현하는 기본적인 단위로 사용되며, 흔히 레고 블록에 비유하기도 합니다. 우리는 하나의 페이지를 다양한 컴포넌트를 조합하여 만들어냅니다.

하드웨어와 달리 소프트웨어는 독립적으로 개발되지 않은 경우가 많고, 독립적으로 개발되었다 하더라도 다른 모듈과의 호환을 생각하지 않고 개발할 수 없습니다. 이는 곧 소프트웨어의 재사용을 어렵게 만들고, 유지보수 비용이 크게 늘어나기 때문입니다. 이러한 특징이 컴포넌트를 개발에서 사용하게 된 배경으로 이어졌습니다.
어쩌면 컴포넌트 사용이 초기 설계에 더 많은 노력을 요구할 수 있지만, 그 대가로 재사용성, 유지보수성, 테스트 용이성 등 여러 이점을 가져올 수 있습니다. 장기적으로 보면 우리가 흔히 말하는 개발 비용을 절감하고, 지속적인 시스템 성장을 가능하게 하는 기반이 되기도 합니다.
고백하자면 저는 리액트를 다루며 컴포넌트에 대해 알게 되었고, 처음에는 그저 리액트에서 컴포넌트를 생성하는 방식만 알고 개발했던 것 같습니다. 컴포넌트 설계에 대한 깊은 고민 없이 기능 구현에만 급급했던 결과, 컴포넌트가 점점 비대해지고 유지보수가 매우 어려워졌을 뿐 아니라 수정 시 여러 사이드 이펙트가 발생했습니다.
만약 혼자가 아니라 팀으로 협업하며 개발한다면 어떨까요? 아마 여러 명의 손을 거치며 개발한 코드가, 명확한 컴포넌트 설계 원칙 없이 진행된다면 생각보다 빠르게 유지보수가 불가능하거나 어려운 상태의 코드가 될 것이라 생각합니다.
이처럼 컴포넌트 설계는 최근 소프트웨어 개발의 핵심으로, 시스템의 복잡성을 관리하고 효율성을 높입니다. 비지니스 요구사항은 끊임없이 변화하며 우리는 이러한 변화에 대응해야 합니다. 컴포넌트 설계는 언젠가, 그리고 누군가가 수정해야 할 코드의 재사용성을 높이고 유지보수성을 높이며 프로젝트의 안정성을 높일 수 있습니다.

각 컴포넌트는 하나의 명확한 역할을 수행해야 한다는 원칙입니다.
이는 기능 변경, 또는 수정이 일어났을 때 파급 효과를 최소화하기 위해 주로 사용합니다. 쉽게 말해 컴포넌트가 변경되는 이유가 한 가지여야 함을 의미합니다.
하나의 컴포넌트가 여러가지 책임을 가지고 있으면, 각기 다른 사유에 의해 코드를 변경해야 하고 이는 유지보수를 어렵게 하는 요인이 됩니다. 뿐만 아니라, 코드를 변경할 때 의도하지 않았던 다른 기능까지 변경되는 연쇄작용을 막을 수 있다는 점도 이점입니다.
기존의 코드를 변경하지 않으면서, 기능을 추가할 수 있도록 설계해야 한다는 원칙입니다.
즉, 확장에 대해서는 개방적(Open), 수정에 대해서는 폐쇄적(Close)이어야 함을 의미합니다. 추상화를 생각하면 되는데, 변하는 것과 변하지 않는 것을 분리하는 것입니다.
예를 들어보자면, 어떤 한 컴포넌트에 기능을 추가해야 할 때 기존 컴포넌트에 코드를 더 추가해서 기능을 구현하기보다는 두 컴포넌트를 아우르는 핵심 로직(변하지 않는 것)을 인터페이스로 구현합니다. 그리고 그 인터페이스를 활용해서 기능을 각각의 컴포넌트로 구현합니다.
이는 기존에 잘 작동하는 코드를 수정할 필요가 없을 뿐 아니라, 컴포넌트 간 의존 관계가 일방적이므로 변화가 미치는 영향을 최소화 할 수 있습니다.
코드의 결합도를 낮추기 위해 사용하는 원칙입니다.
고수준 모듈은 저수준 모듈에 의존해서는 안되며, 필요한 경우 양쪽 모두 추상화에 의존해야 한다는 원칙입니다. 즉 둘 다 추상화 계층(인터페이스)에 의존하도록 설계함으로 결합도를 낮춥니다.
사실 처음에는 이해가 잘 되지 않지만, 예를 들어 api를 연동하는 작업을 진행한다고 했을 때 고수준 모듈은 화면을 그리는 모듈입니다. 그리고 저수준 모듈은 axios 등을 활용하여 실제로 데이터를 서버에서 가져오는 모듈입니다.
이때 만약 고수준 모듈에서 바로 저수준 모듈을 가져와서 사용하면 결합도가 높아집니다. 화면 로직을 테스트 할 때 네트워크 통신이 발생하기 때문에 테스트의 어려움 뿐 아니라 만약 추후에 HTTP 클라이언트 라이브러리가 달라진다면, 모든 화면 컴포넌트도 같이 수정해야 합니다.
그런데 만약 사이에 추상화 파일을 만들어서, 사용자 목록을 가져오는 getUser()를 정의하여 중간에서 사용한다면 어떨까요? 결합도를 낮추고, 위에서 언급한 문제들 대부분을 해결할 수 있을 것입니다.
뿐만 아니라, 우리가 Props drilling을 막기 위해 사용했던 Context 객체도 DIP의 예시로 볼 수 있습니다.
여기서부터는 디자인 패턴과 연결되는 부분인데, 위 핵심 원칙들을 기반으로 어떻게 컴포넌트들을 효율적으로 분리할 수 있을지 2가지만 간단히 알아보겠습니다.
UI를 렌더링하는 프레젠테이션 컴포넌트와 데이터 로직을 담은 컨테이너 컴포넌트를 분리하는 패턴입니다. 위에서 언급했던 단일 책임 원칙과 이어지는 부분이 많다고 생각합니다.
프레젠테이션 컴포넌트는 데이터를 관리하지 않고, 컨테이너 컴포넌트에게 전달 받아 UI를 구현하는 역할에만 집중합니다. 반대로 컨테이너 패턴은 화면에 아무것도 렌더링하지 않고, UI 구현에 필요한 로직을 수행하여 데이터를 전달해주는 역할에 집중합니다.
이제 컴포넌트가 마치 부품이나 레고처럼 조립되어 페이지가 구현된다는 것과, 컴포넌트는 재사용을 위해 만든다는 사실은 알고 있습니다. 이제 이러한 컴포넌트들을 잘 정리해서 효율적으로 사용하기만 하면 되는데, 이때 활용할 수 있는 것이 '아토믹 디자인 패턴' 입니다.
컴포넌트도 계층이 분명 존재합니다. 아토믹 디자인 패턴은 가장 작은 컴포넌트 단위를 원자로 설정하고, 이를 바탕으로 상위 컴포넌트를 만들어 코드 재사용을 최대화하는 방법론입니다.
가장 작은 원자 컴포넌트는 레이블, 텍스트, 컨테이너, 버튼 등이 있고 이를 조립하여 분자 컴포넌트인 입력 폼을 만들고, 또 분자 컴포넌트를 조합하여 상위 컴포넌트를 만드는 방식입니다.
이를 통해 유지보수성을 향상시키고, 컴포넌트 재사용성 증대, UI 일관성 유지, 개발 과정에서의 유연성 등 다양한 이점을 가질 수 있습니다.

이번 아티클을 작성하던 중, 컴포넌트를 분리하는 기준에 대해 잘 다뤄주신 아티클을 접하게 되었습니다. 컴포넌트를 언제 분리하면 좋을지 잘 정리해놓은 글이라서, 한번씩 읽어보시면 도움이 되실 것 같아 공유드리며 글을 마치겠습니다!
[ 추가로 읽어보면 좋을 아티클 공유 ]
https://tech.kakaoent.com/front-end/2024/240116-common-component/
이번 글을 읽으며 여러 가지를 다시 생각해보는 계기가 되었습니다!
그동안 컴포넌트를 “일단 나누면 좋다니까 나누자”라는 관성에 따라 분리해온 적이 많았는데, 정작 왜 컴포넌트 설계가 중요한지에 대해 깊이 고민해본 적은 거의 없었던 것 같다는 생각이 들었습니다.
특히 단일 책임 원칙은 함수 설계에서만 적용되는 개념이라고만 생각했는데(;;;) 컴포넌트에도 그대로 적용된다는 점이 인상 깊었습니다... 하하
(돌이켜보면 비대해진 컴포넌트 대부분이 여러 역할을 한꺼번에 떠안고 있었기 때문인데, 그동안 원칙 없이 만들다 보니 유지보수가 어려워진 부분을 자연스럽게 떠올리게 되네요......!!!!!)
OCP나 DIP 같은 개념도 사실 큰 틀만 알고 있었지, 제대로 이해하지 못한 채 그냥 따라 해보자... 정도로 접근했던 것 같다는 점도 스스로 느꼈습니다. 글에서 예시와 함께 설명해주신 부분 덕분에 그동안 추상적이던 개념들이 실제 개발 흐름과 연결되는 것 같아요 ㅎㅎㅎ
또 한 가지 공감했던 지점은 컴포넌트 설계 패턴에 관한 내용이었습니다...!!!
지금까지는 정확한 기준 없이 어떤 때는 UI와 로직을 분리하자는 명목으로, 어떤 때는 재사용이 필요할 것 같다는 이유로 컴포넌트를 분리해왔는데, 이렇다 보니 폴더 구조나 책임 분배가 일관적이지 못하고, 비슷한 목적의 컴포넌트들조차 서로 다른 방식으로 만들어지는 경우가 있었습니다. . .
이번 글과 첨부해주신 참고 자료를 읽으면서 컴포넌트 분리에도 분명한 원칙과 기준이 존재하고, 그 기준에 따라 구조적 일관성이 생기며 프로젝트 전체의 안정성으로 이어질 수 있다는 점을 다시 느끼게 되었습니다.
좋은 아티클 감사합니닷... 😎😎😎
저번 주에도 저랑 같이하신 거같은데, 고생하셨어요 !
컴포넌트라는 개념을 단순히 UI 조각을 만들어 재사용하는 수준으로만 이해하고 있었던 것 같았는데, 글을 통해 컴포넌트 설계가 소프트웨어 유지보수성과 확장성의 핵심 원칙이라는 점을 다시 한 번 생각해볼 수 있었습니다 !!
특히 인상 깊었던 부분은 컴포넌트의 본질이 단순히 코드를 조각내는 것이 아니라, SRP, OCP, DIP와 같은 객체지향 설계 원칙을 바탕으로 시스템 전체의 변경 비용을 줄이는 구조를 만드는 일이라는 점이었습니다. 변화가 잦은 프론트엔드 환경에서 이러한 원칙들을 적용하는 것은 결국 팀과 프로젝트를 지키는 일이라는 것이 와닿았던 것같아요 .
또 프론트엔드 아키텍처에대해 추가적으로 공부해보고 싶었는데, 추가자료 공유 감사드립니다 !
컴포넌트를 설계할 때 생각해야될 원칙들을 잘 정리해주신것 같아요. 이번에 아티클을 읽으면서 내가 컴포넌트를 잘 설계 하고 있나 라는 생각이 들게 될 정도로 깔끔하게 잘 정리해주신것 같습니다.
항상 개방, 폐쇄 원칙을 잘 지키면서 개발을 해야지 하는데 예를 들어 button이 있으면 폰트 사이즈를 props로 받는다는 상황에서 사이즈를 sm , md, lg 로 했다가 하나가 더 추가되거나 중간 사이즈가 끼었을 때 sm, b_md, md, lg 이렇게 어거지로 끼워맞추거나 개방 폐쇄 원칙을 깨고 기존 md 사이즈를 모든 파일에서 수정하는 등의 일들이 생겼던 경험이 있네요.. 컴포넌트 설계는 너무 어려운것 같아요 ㅠㅠ
추가로 컴포넌트를 만들 때 제가 생각하며 구현했던것들이 어떤 원칙인지 정리하는 계기가 되었던것 같아요.
좋은 아티클인것 같아요. 고생많으셨습니다 굳굳!!
컴포넌트의 기본 개념부터 설계 원칙, 그리고 패턴까지 잘 정리되어 있네요!! 컴포넌트를 레고 블록에 비유하신 것 공감합니다. 아토믹 디자인 패턴도 재사용성 측면에서 강력한 도구라고 생각이 듭니다.
특히 의존성 역전 원칙(DIP) 설명 부분에서 API 호출과 고수준/저수준 모듈 예시 axios 를 들어 설명해 주시니 이해가 잘되었습니다. 컴포넌트 설계도 변하는 것과 변하지 않는 것을 구분하는 게 핵심이라는 것을 다시 한번 깨닫고 갑니다.
좋은 아티클 공유 감사합니다!