컴포넌트를 잘 만들고 싶다는 마음은 누구나 있다. 그런데 막상 코드를 열면 Props가 열 개가 넘는 버튼, 500줄짜리 페이지 컴포넌트, 이름만 봐서는 무슨 역할인지 알 수 없는 UniversalCard가 기다리고 있다. 이 글은 그 간극을 메우기 위한 세 가지 원칙—재사용 가능한 설계, 관심사의 분리, 인터페이스 설계—을 하나의 흐름으로 엮어보려는 시도다.
재사용은 목적이 아니라 결과다
velog의 「재사용 가능한 컴포넌트는 어떻게 설계하는가」는 이 주제를 정확한 문장으로 시작한다. "재사용은 목적이 아니라 설계의 결과다." 재사용 자체를 목표로 삼는 순간, 컴포넌트는 모든 상황을 처리하려다 오히려 아무 데도 쓰기 불편한 괴물이 된다.
문제는 '미래를 위한 추상화' 충동에 있다. "혹시 나중에 필요할 수 있으니"라는 이유로 Props를 미리 열어두면, 시간이 지날수록 조건 분기가 쌓이고 유지보수 비용은 재사용 이득을 훌쩍 넘어선다. MovieCard는 영화 도메인에 묶여 있어도 충분히 좋은 컴포넌트다. 도메인에 맞는 책임을 명확히 가지고 있다면, 재사용 범위가 좁아도 설계가 나쁜 게 아니다.
반면 진짜로 재사용 가능해지는 컴포넌트는 특정 맥락에 대한 의존성을 최소화한 결과물이다. LoginButton이 Button으로 일반화되는 과정, children으로 내용 결정권을 사용자에게 넘기는 Card의 구조—이것들이 재사용성을 의도적으로 설계한 게 아니라, 역할을 명확히 하다 보니 자연스럽게 얻어진 특성이다.
관심사를 나누면 수정 범위가 좁아진다
같은 velog 시리즈의 「관심사의 분리란 무엇인가」는 실무에서 가장 자주 마주치는 문제를 직접 건드린다. API 호출·상태 관리·UI 렌더링을 한 컴포넌트가 전부 담당하는 구조. 처음엔 빠르게 동작하지만, 기능이 늘어날수록 파일은 커지고 어디를 고쳐야 하는지 파악하기조차 어려워진다.
관심사 분리의 실질적인 이득은 '수정 범위'에 있다. API 엔드포인트가 /movies에서 /v2/movies로 바뀔 때, 데이터 패칭 로직이 fetchMovies 함수 하나로 분리되어 있다면 수정은 한 줄이다. 반면 같은 API 호출이 여러 컴포넌트에 흩어져 있다면 전체 코드베이스를 뒤져야 한다. React Query가 서버 상태를 별도로 관리하도록 설계된 이유도 여기에 있다. 서버 데이터와 UI 상태는 성격이 다르고, 그 차이를 인정하는 것이 좋은 아키텍처의 출발점이다.
MovieCard 하나가 이미지 렌더링·정보 표시·즐겨찾기 기능을 모두 처리하던 구조를 MovieImage, MovieInfo, FavoriteButton으로 나눈다고 생각해보자. 각 컴포넌트는 서로를 알 필요가 없다. 결합도가 낮아지면 테스트도 쉬워진다. useMovies 훅이 분리되어 있으면 UI 없이 로직만 단독으로 검증할 수 있다. 관심사 분리는 코드 미학이 아니라 유지보수 비용의 문제다.
인터페이스는 구현이 아니라 의도를 말해야 한다
세 번째 원칙은 「인터페이스 설계란 무엇인가」에서 다룬다. Props는 컴포넌트의 계약(Contract)이다. 계약이 자주 바뀌면 그 컴포넌트를 사용하는 모든 코드가 영향을 받는다. type="primary"에서 variant="primary"로 이름 하나 바꾸는 것도, 프로젝트 규모가 크다면 리팩토링 세션 전체를 잡아먹는 작업이 된다.
color="blue"와 variant="primary"의 차이는 단순한 네이밍 취향이 아니다. 전자는 구현 정보를 노출하고, 후자는 의도를 전달한다. 디자인 토큰이 바뀌어 파란색이 다른 색으로 대체될 때, variant="primary"를 쓰는 코드는 수정할 필요가 없다. 인터페이스가 구현 세부사항으로부터 독립되어 있기 때문이다.
API First 설계 방식—구현보다 사용 코드를 먼저 작성하는 접근—은 이 관점을 실천 루틴으로 만든다. 먼저 <Button variant="primary" size="large">저장</Button>을 쓴 다음, 그 인터페이스를 충족시키는 구현을 채운다. 사용성을 중심에 두고 설계가 시작된다. 디자인 시스템의 버튼 컴포넌트들이 서로 다른 회사, 다른 팀에서 만들어졌음에도 비슷한 Props 구조를 가지는 이유가 여기에 있다. 버튼이 해결해야 하는 문제의 본질이 같기 때문이다.
세 원칙이 만나는 지점
이 세 가지는 사실 하나의 흐름이다. 역할을 명확히 하면(관심사 분리) → 컴포넌트는 자연스럽게 재사용 가능한 단위로 정리되고(재사용 설계) → 외부에 노출되는 Props는 의도를 담은 계약이 된다(인터페이스 설계). 어느 하나만 잘해서는 효과가 반감된다. 관심사를 잘 나눴어도 Props가 구현 세부사항으로 가득하다면 유지보수 지점은 컴포넌트 내부에서 사용 코드 전체로 번진다.
디자인 시스템을 구축하거나 팀의 컴포넌트 컨벤션을 잡을 때, 이 흐름을 기준으로 삼으면 기준이 명확해진다. "이 Props가 구현 정보를 노출하고 있지는 않은가", "이 컴포넌트가 두 가지 이상의 책임을 지고 있지는 않은가", "재사용을 위해 설계하는 건지, 명확한 역할 때문에 자연스럽게 재사용 가능해진 건지"—이 세 질문이 코드 리뷰 체크포인트가 될 수 있다.
좋은 컴포넌트는 처음부터 완벽하게 설계되지 않는다. MovieCard에 평점이 붙고 즐겨찾기가 붙는 과정처럼, 요구사항이 추가될 때마다 역할을 다시 묻고 인터페이스를 조금씩 다듬는 것—그 반복이 결국 좋은 컴포넌트를 만들어낸다. 설계는 한 번의 결정이 아니라 지속적인 판단의 축적이다.