Tech
단일책임원칙(SRP)을 충족하는 React 개발
2025년 11월 6일
혹시 SOLID 원칙에 대해 들어보셨나요? 개발을 접하신 분이라면 자세히는 몰라도 한 번쯤 이름은 들어보셨을 거예요. SOLID 원칙은 객체지향 설계에서 지켜야 할 5가지 소프트웨어 개발 원칙(SRP, OCP, LSP, ISP, DIP)을 말합니다.
SRP(Single Responsibility Principle) : 단일 책임 원칙
OCP(Open Closed Principle) : 개방 폐쇄 원칙
LSP(Liskov Substitution Principle) : 리스코프 치환 원칙
ISP(Interface Segregation Principle) : 인터페이스 분리 원칙
DIP(Dependency Inversion Principle) : 의존 역전 원칙
이런 원칙들을 지키면 좋은 객체지향 설계에 가까워질 수 있습니다. 그런데 'React에 객체지향이라니?' 하고 의아하게 생각하실 수도 있을 것 같아요. 하지만 우리는 이런 원칙들을 조금 더 넓은 시야로 바라볼 필요가 있다고 생각합니다.
사실 저는 주니어 개발자 시절부터 꽤 오랫동안 설계 원칙에 갇혀 있었습니다. 하나의 원칙을 프로젝트에 적용하면 그것을 엄격하게 지키려고만 했지, '왜 지켜야 하는가'에 대해서는 제대로 생각하지 못했어요. 그러다 보니 오히려 원칙을 지키는 게 불편해지고, 어느 순간부터는 원칙을 의도적으로 멀리하게 됐습니다. 코드를 작성하는 데 불편하고 시간도 오래 걸렸거든요. 막상 코드를 작성하고 나면 그렇게 마음에 들지도 않았어요.
하지만 원칙을 멀리한 채 개발하다 보니, 오히려 다시 원칙을 찾게 되는 상황들이 생겼습니다. 왜일까요? 개발 원칙에는 좋은 소프트웨어를 만들기 위한 개발 철학이 담겨있기 때문입니다. 나보다 먼저 이 길을 걸은 개발자들의 고민과 해결 방법들이 개발 원칙으로 만들어진 것이죠. 구체적인 지침 자체는 내 상황에 맞지 않을 수 있어도, 그 지침이 만들어지기까지의 철학은 어떤 소프트웨어 개발에도 유효할 수 있습니다. 한 발 물러서서 개발 원칙을 바라보니, 필요한 시점에 필요한 철학을 적절히 적용할 수 있게 되었습니다.
다시 SOLID 원칙으로 돌아와 보면, 이것은 객체지향 설계를 위한 원칙이지만 그 이면의 개발 철학을 들여다보면 어떤 소프트웨어에도 적용 가능한 통찰이 담겨 있습니다. 이번 글에서는 SOLID 원칙 중 하나인 SRP(Single Responsibility Principle, 단일 책임 원칙)에 대해 살펴보고, 실제로 React 개발에 어떻게 적용했는지 이야기해보려 합니다.
단일 책임 원칙의 철학
단일 책임 원칙(SRP)의 핵심은 간단합니다. "하나의 모듈(클래스, 함수, 컴포넌트)은 하나의 책임만 가져야 한다." 여기서 '책임'이란 '변경의 이유'를 의미합니다. 로버트 C. 마틴은 이를 "하나의 모듈은 하나의, 오직 하나의 액터(사용자)에 대해서만 책임져야 한다"고 표현했습니다.
왜 이게 중요할까요? 여러 책임이 하나의 모듈에 섞여 있으면, 한 가지를 수정할 때 다른 것까지 영향을 받게 됩니다. 책임이 명확하게 분리되어 있으면 변경의 파급 효과를 최소화할 수 있고, 코드를 이해하고 테스트하기도 훨씬 쉬워집니다.
실생활에서의 단일 책임 원칙

우리가 개발 과정을 설명할 때 자동차를 예시로 자주 사용합니다. 자동차는 각 책임 단위인 부품으로 잘 분리되어 있습니다. 에어컨이 고장나면 에어컨만 수리하면 되고, 브레이크가 고장나면 브레이크만 교체하면 됩니다. 각 부품에 문제가 생겼을 때 해당 부품만 손보면 되는 구조죠.

그렇다면 단일 책임 원칙을 지키지 못해 에어컨, 브레이크, 엔진이 하나의 통합 모듈로 결합되어 있다면 어떻게 될까요? 에어컨 필터만 교체하려 해도 전체 모듈을 분해해야 합니다. 작업 중 실수로 브레이크 라인을 건드려 브레이크액이 샐 수도 있고, 엔진 배선을 잘못 만져 시동이 걸리지 않을 수도 있습니다. 단순한 에어컨 필터 교체가 브레이크나 엔진 같은 핵심 기능에 문제를 일으킬 위험이 생기는 거죠. 당연히 수리 시간도 길어지고, 비용도 훨씬 비싸집니다. 여기에 에어컨 부품이 개선되어 새로운 에어컨 부품이 개발되었다고 해볼까요? 별도 모듈로 사용하고 있다면 브레이크와 엔진은 기존 부품을 그대로 사용하면 되는데, 모듈이 결합되어 있기 때문에 새 에어컨 부품을 사용하기 위해서는 새 에어컨, 브레이크, 엔진이 결합된 새로운 모듈을 만들어 사용해야 합니다. 기존 모듈은 더이상 사용할 수 없어지겠죠.
소프트웨어 개발도 마찬가지입니다. 단일 책임 원칙을 지키지 못한다면 동일한 문제가 발생합니다.
작은 수정에도 큰 비용이 발생합니다. (유지보수 비용 증가)
코드 수정 중 예상치 못한 사이드 이펙트가 발생합니다.
수정이 두려워 코드를 수정하기보다는 코드를 추가하게 되고, 코드가 점점 복잡해집니다.
코드 재사용이 어려워집니다. (여러 책임이 얽혀있기 때문에)
결국 단일 책임 원칙은 유지보수 비용을 줄이면서, 자연스럽게 재사용성도 높여주는 중요한 원칙입니다. React에서는 컴포넌트 재사용 빈도가 높기 때문에 단일 책임 원칙을 지키면 재사용성 측면에서 큰 이점을 만들어낼 수 있습니다.
React 개발에서 '책임'이란?
React 개발은 결국 함수 개발이라고 할 수 있습니다. 비즈니스 로직은 말할 것도 없이 함수로 개발하고 UI조차도 함수로 개발하는 특징을 갖고 있죠. 그리고 함수가 하는 일이 곧 책임입니다. 그래서 React에서의 책임은 ‘함수가 어떤 비즈니스 로직을 수행하는가' 뿐만 아니라 ‘어떤 UI를 어떻게 그려내는가’를 포함한다고 할 수 있습니다.
React에서 단일 책임 원칙 지키기
비즈니스 로직과 UI 분리
가장 먼저 책임을 분리할 수 있는 지점은 비즈니스 로직과 UI를 분리하는 것입니다. React는 비즈니스 로직과 UI를 모두 함수로 표현하기에 이를 합쳐서 사용하게 되는 경우가 많습니다.

간단하게 예를 들어 위와 같은 MyProfile 컴포넌트가 있다고 해보겠습니다. 언뜻 보기에는 내 정보를 화면에 보여주는 단일 책임 컴포넌트처럼 오해하기 쉽습니다. 하지만 이 컴포넌트는 '내 정보를 호출하는 로직'과 '프로필 UI 렌더링 로직' 두 가지가 강하게 결합되어 있습니다.
이렇게 되면 어떤 문제가 발생할까요?
가장 먼저 발생하는 문제는 재사용이 어려워진다는 것입니다. 동일한 UI를 사용하는 화면이 있는데 내 정보가 아니라 다른 사용자의 프로필을 보여줘야 한다고 가정해볼까요? 현재 MyProfile은 내 정보를 화면에 그려주는 역할을 하기 때문에 이 코드를 재사용할 수 없습니다. 그럼 아래와 같은 컴포넌트를 새로 만들어야 할 겁니다.

컴포넌트를 분석해보니 '유저 정보를 호출하는 로직'과 '프로필 UI 렌더링 로직'이 결합되어 있네요. 뭔가 이상함을 느끼셨나요? 같은 '프로필 UI 렌더링' 책임이 두 컴포넌트에 중복되어 존재합니다. 단지 두 개가 아닙니다. 앞으로 동일한 UI를 사용할 때마다 매번 새로운 컴포넌트를 만들어야 하고, 앞으로 몇 개가 더 생성될지 모릅니다.
이는 결국 유지보수의 문제로 이어집니다. "프로필 UI에서 email이 없으면 '-'를 보여주세요" 같은 간단한 수정에도 수많은 컴포넌트들을 일일이 찾아서 수정해야 하고, 누락되는 부분이 생길 가능성이 큽니다.
반대로 다른 컴포넌트에서 유저정보를 가져다 사용하는 것 처럼 비즈니스 로직이 재사용되는 경우에도 매번 동일한 코드를 작성해줘야 하겠죠.
이처럼 비즈니스 로직과 UI의 결합은 언뜻 보면 자연스러워 보여도 실제로는 서로 다른 책임의 결합으로 이루어져 있음을 이해해야 합니다.
그럼 어떻게 수정하면 좋을까요? 처음 이야기했던 대로 비즈니스 로직과 UI를 분리하면 됩니다. 비즈니스 로직은 Custom Hook으로, UI는 별도 컴포넌트로 분리해보겠습니다.

이제 책임을 나누었으니 완성된 함수를 사용해보겠습니다.

어떠신가요? 이제 ProfileCard는 UI의 변경에만 대응하면 되는 단일 책임을 가진 컴포넌트가 되었습니다. 이렇게 비즈니스 로직과 UI를 분리하게 되면 여러 가지 장점이 따라옵니다.
재사용성 향상
ProfileCard는 어디서든 사용 가능합니다.
새로운 페이지에서 프로필을 보여줘야 한다면? ProfileCard만 재사용하면 됩니다.
내 정보를 가져오는 비즈니스 로직이 필요하면 useMyInfo를 재사용하면 됩니다.
유지보수 용이
useMyInfo , useUser : "데이터를 어떻게 가져올 것인가"에 대한 책임
ProfileCard : "프로필을 어떻게 보여줄 것인가"에 대한 책임
MyProfile , UserProfile : "어떤 데이터를 어떤 UI로 조합할 것인가"에 대한 책임
각 모듈이 하나의 명확한 책임만 가지고 있어서, 변경의 이유도 하나씩만 가집니다. 유지보수 시점에 어떤 책임을 수정할지 확인하고 수정하면 됩니다.
병렬 개발이 가능하다
API 서버 개발이 늦어지는 상황에서도 UI를 완성할 수 있고, 추후 비즈니스 로직 작성이 UI 컴포넌트를 수정하지 않습니다.
반대로 디자인이 늦어지는 상황에서도 비즈니스 로직을 먼저 작성할 수 있습니다.
각각의 책임을 쉽게 테스트할 수 있다.

2. UI 세분화
비즈니스 로직을 분리했다면, 이제 UI 자체도 더 작은 책임으로 나눌 수 있습니다. 앞서 만든
ProfileCard를 다시 살펴볼까요?

이 컴포넌트는 '프로필 카드를 렌더링한다'는 하나의 책임을 가진 것처럼 보입니다. 실제로 어떤 상황에서는 하나의 책임이 맞을 수 있습니다. 하지만 조금 더 들여다보면 상황에 따라 여러 UI 요소들이 섞여 있다고 할 수 있습니다.
아바타 이미지 표시
사용자 이름 표시
이메일 표시
전체 레이아웃 구성
만약 다음과 같은 요구사항이 생긴다면 어떨까요?
"댓글 섹션에서도 사용자 아바타와 이름을 보여주세요"
"헤더에 아바타만 동그랗게 표시해주세요"
"사용자 정보 편집 폼에서 이메일 입력란 스타일을 프로필과 동일하게 해주세요"
현재 구조에서는 이런 작은 UI 요소들을 재사용하기 어렵습니다. 책임을 분리한 순간 ProfileCard에 이미 여러 책임들이 섞여있기 때문이죠. 그래서 ProfileCard에서 코드를 복사해와서 사용하거나 새로작성해야 할 겁니다. 이는 디자인팀도 Figma를 사용해 디자인 요소를 컴포넌트화하는 현재 시점에서, 비즈니스 로직과 UI의 결합과 마찬가지로 유지보수 문제를 발생시킬 수 있습니다.
UI 컴포넌트도 비즈니스 로직처럼 작은 책임 단위로 나눌 수 있습니다. ProfileCard를 실제로 책임 단위로 쪼개보겠습니다.

이제 각 요소를 독립적으로 재사용할 수 있습니다.

이렇게 UI 로직도 책임 단위로 분리하게 되면 재사용성과 유지보수성을 더 좋게 만들 수 있습니다. 이렇게 UI 요소를 책임 단위로 분리하는 대표적인 패턴이 Atomic Design Pattern 입니다.
주의해야 할 점은 반드시 모든 UI를 작은 컴포넌트 단위로 쪼갤 필요는 없다는 것입니다. 책임의 단위는 각 회사마다 다르고, 개발자마다 느끼는 책임의 범위도 다릅니다. 중요한 것은 실제로 재사용되는 단위, 독립적으로 변경되는 단위를 기준으로 적합한 책임의 단위를 찾아내고 관리하는 것입니다. 모든 요소를 강박적으로 쪼갤 필요는 없습니다.
비즈니스 로직 세분화
비즈니스 로직과 UI를 분리했다고 해서 끝이 아닙니다. 비즈니스 로직 자체도 여러 책임으로 나눌 수 있습니다. Custom Hook 안에서도 여러 가지 일을 한꺼번에 처리하고 있다면, 그것 역시 단일 책임 원칙을 위반하는 것입니다.
예를 들어 장바구니 기능을 개발한다고 가정해봅시다.

이 Hook은 다음과 같은 여러 책임을 가지고 있습니다.
장바구니 데이터 가져오기
가격 계산 (총액, 할인, 배송비)
장바구니 아이템 관리
이렇게 되면 어떤 문제가 발생할까요?
주문 페이지에서 가격 계산만 필요한데, useCart를 사용하면 불필요한 장바구니 데이터 fetching까지 실행됩니다
가격을 계산하는 코드는 외부 의존성이 없는 순수 함수 성격의 코드인데, 이를 테스트하기 위해서는 API mocking까지 해야합니다.
그럼 어떻게 책임단위로 분리할 수 있을까요?

로직을 분리하면 이제 필요한 곳에서 조립해 사용할 수도 있고, 각 모듈을 독립적으로 재사용 할 수도 있습니다. 테스트하기도 훨씬 수월해졌죠.

이렇게 비즈니스 로직을 적당한 책임 단위로 분리해 재사용성을 높이고 유지보수를 용이하게 만들었습니다. 비즈니스 로직도 UI 로직과 마찬가지로 억지로 작은 단위로 분리하려고 힘쓸 필요는 없습니다. 우선 적당한 책임 단위로 만들고, 책임 분리가 필요한 시점에 분리해도 괜찮습니다. 가장 중요한건 분리가 필요하다고 느끼는 시점에는 반드시 분리를 해 주는 것이라고 생각합니다.
마치며
간단하게 단일 책임 원칙이 React에 어떤식으로 적용될 수 있는지 살펴봤습니다. 사실 위에 적은 이점 외에도 책임을 분리하면 좋은 점이 하나 더 있다고 생각합니다. 바로 AI에게 일을 맡길 때 굉장히 좋은 결과물로 나올 때가 많다는 것입니다. 이것저것 여러 책임을 부여한 컴포넌트를 AI에 만들어달라고 했을 때와 책임단위로 일을 분리한 뒤 한 개씩 AI에 만들어달라고 했을 때, 후자가 훨씬 더 좋은 결과물을 만들어 낼 때가 많았습니다. AI 생성물을 관리하는 입장에서도 후자가 훨씬 유리하겠죠.
단일 책임 원칙은 객체지향의 전유물이 아닙니다. SOLID 원칙도 그렇고 다른 개발원칙도 그렇습니다. React 개발에서도, 아니 어떤 개발에서도 적용할 수 있는 철학입니다. 중요한 건 원칙을 맹목적으로 따르는 게 아니라, 그 이면의 철학을 이해하고 상황에 맞게 적용하는 것입니다.
여러분의 컴포넌트는 너무 많은 책임을 떠안고 있진 않나요? 컴포넌트가 힘들어하고 관리자도 힘들어하고 있진 않은지요? 한 번 돌아보는 시간을 가져보시면 유익한 시간이 되리라 확신합니다.
결국 좋은 설계는 원칙을 맹목적으로 따르는 것이 아니라, 원칙을 ‘이해한 뒤 선택적으로 적용하는 것’에서 시작됩니다.
By 방경민 ㅣ TVPP/FE Leader ㅣ 60Hertz
