Tech
[블로그] 단일책임원칙(SRP)을 충족하는 React 개발
2025년 11월 14일
혹시 SOLID 원칙에 대해 들어보셨나요?
개발을 접하신 분이라면 자세히는 몰라도 한 번쯤 이름은 들어보셨을 거예요.
SOLID 원칙은 객체지향 설계에서 지켜야 할 다섯 가지 소프트웨어 개발 원칙(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를 사용하는 분들이라면 이런 생각이 들 수도 있습니다.
“객체지향 원칙을 함수 기반 React에 어떻게 적용하지?”
식스티헤르츠는 관리자 도구, 대시보드, 사용자 웹 서비스 등 다양한 영역에서 React를 활용하고 있습니다.
React는 빠르고 선언적인 개발을 가능하게 하지만, 동시에 SOLID 같은 설계 원칙을 놓치기 쉬운 환경이기도 합니다.특히 일정에 쫓기다 보면 "일단 되게 만들고 나중에 리팩토링하자"는 생각으로 컴포넌트 하나에 API 호출, 상태 관리, 비즈니스 로직, UI 렌더링을 모두 집어넣게 됩니다. 당장은 빠르게 느껴지지만, 시간이 지나면 그 '나중'은 오지 않고 코드는 점점 더 복잡해집니다. 결국 새로운 기능을 추가할 때마다 기존 코드를 건드리기 두려워지고, 수정 대신 코드를 추가하는 방식으로 문제를 회피하게 되죠.
사실 저는 주니어 개발자 시절부터 꽤 오랫동안 설계 원칙에 갇혀 있었습니다. 하나의 원칙을 프로젝트에 적용하면 그것을 엄격하게 지키려고만 했지, '왜 지켜야 하는가'에 대해서는 제대로 생각하지 못했어요. 그러다 보니 오히려 원칙을 지키는 게 불편해지고, 어느 순간부터는 원칙을 의도적으로 멀리하게 됐습니다. 코드를 작성하는 데 불편하고 시간도 오래 걸렸거든요. 막상 코드를 작성하고 나면 그렇게 마음에 들지도 않았어요.
하지만 원칙을 멀리한 채 개발하다 보니, 오히려 다시 원칙을 찾게 되는 상황들이 생겼습니다. 왜일까요? 개발 원칙에는 좋은 소프트웨어를 만들기 위한 개발 철학이 담겨있기 때문입니다. 나보다 먼저 이 길을 걸은 개발자들의 고민과 해결 방법들이 개발 원칙으로 만들어진 것이죠. 구체적인 지침 자체는 내 상황에 맞지 않을 수 있어도, 그 지침이 만들어지기까지의 철학은 어떤 소프트웨어 개발에도 유효할 수 있습니다. 한 발 물러서서 개발 원칙을 바라보니, 필요한 시점에 필요한 철학을 적절히 적용할 수 있게 되었습니다.이 글에서는 그런 문제의식에서 출발해, SOLID의 첫 번째 원칙인 SRP(단일 책임 원칙)를 React 코드에 어떻게 적용할 수 있는지 살펴보려 합니다
단일 책임 원칙의 철학
단일 책임 원칙(SRP)의 핵심은 간단합니다. "하나의 모듈(클래스, 함수, 컴포넌트)은 하나의 책임만 가져야 한다." 여기서 '책임'이란 '변경의 이유'를 의미합니다. 로버트 C. 마틴은 이를 "하나의 모듈은 하나의, 오직 하나의 액터(사용자)에 대해서만 책임져야 한다"고 표현했습니다.
왜 이게 중요할까요? 여러 책임이 하나의 모듈에 섞여 있으면, 한 가지를 수정할 때 다른 것까지 영향을 받게 됩니다. 책임이 명확하게 분리되어 있으면 변경의 파급 효과를 최소화할 수 있고, 코드를 이해하고 테스트하기도 훨씬 쉬워집니다.
실생활에서의 단일 책임 원칙

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

그렇다면 단일 책임 원칙을 지키지 못해 에어컨, 브레이크, 엔진이 하나의 통합 모듈로 결합되어 있다면 어떻게 될까요? 에어컨 필터만 교체하려 해도 전체 모듈을 분해해야 합니다. 작업 중 실수로 브레이크 라인을 건드려 브레이크액이 샐 수도 있고, 엔진 배선을 잘못 만져 시동이 걸리지 않을 수도 있습니다. 단순한 에어컨 필터 교체가 브레이크나 엔진 같은 핵심 기능에 문제를 일으킬 위험이 생기는 거죠. 당연히 수리 시간도 길어지고, 비용도 훨씬 비싸집니다.
여기에 에어컨 부품이 개선되어 새로운 에어컨 부품이 개발되었다고 해볼까요? 별도 모듈로 사용하고 있다면 브레이크와 엔진은 기존 부품을 그대로 사용하면 되는데, 모듈이 결합되어 있기 때문에 새 에어컨 부품을 사용하기 위해서는 새 에어컨, 브레이크, 엔진이 결합된 새로운 모듈을 만들어 사용해야 합니다. 기존 모듈은 더이상 사용할 수 없어지겠죠.
소프트웨어 개발도 마찬가지입니다. 단일 책임 원칙을 지키지 못한다면 동일한 문제가 발생합니다.
작은 수정에도 큰 비용이 발생합니다 (유지보수 비용 증가)
코드 수정 중 예상치 못한 사이드 이펙트가 발생합니다
수정이 두려워 코드를 수정하기보다는 코드를 추가하게 되고, 코드가 점점 복잡해집니다
코드 재사용이 어려워집니다. (여러 책임이 얽혀있기 때문에)
결국 단일 책임 원칙은 유지보수 비용을 줄이면서, 자연스럽게 재사용성도 높여주는 중요한 원칙입니다. React에서는 컴포넌트 재사용 빈도가 높기 때문에 단일 책임 원칙을 지키면 재사용성 측면에서 큰 이점을 만들어낼 수 있습니다.
React 개발에서 SOLID 원칙을 놓치기 쉬운 이유
식스티헤르츠는 React를 주로 관리자 도구와 대시보드, 그리고 사용자 대면 웹 서비스 개발에 사용하고 있습니다. 개발 업무 중 웹 개발이 차지하는 비중이 많기 때문에 javascript를 적극적으로 활용하게 되고, 속도와 유지보수성을 모두 살리기 위해 선언적 개발을 가능하게 만들어주는 React를 주로 활용하고 있습니다. 여러 상황에서 React는 훌륭한 선택이지만, 동시에 SOLID 원칙 같은 설계 원칙을 간과하게 만드는 특성도 가지고 있습니다.
JavaScript와 React는 강력한 자유도를 제공합니다. 함수 안에서 무엇이든 할 수 있으며, 컴포넌트 하나에 로직과 UI를 모두 담을 수 있습니다. 이런 자유로움 덕분에 빠르게 기능을 구현할 수 있지만, 역설적으로 '잘못된 설계'도 쉽게 만들어집니다. Java나 C# 같은 언어에서는 클래스 구조와 타입 시스템이 어느 정도 설계를 강제하지만, React에서는 모든 것이 '함수'이기 때문에 책임의 경계가 흐려지기 쉽습니다.
특히 일정에 쫓기다 보면 "일단 되게 만들고 나중에 리팩토링하자"는 생각으로 컴포넌트 하나에 API 호출, 상태 관리, 비즈니스 로직, UI 렌더링을 모두 집어넣게 됩니다. 당장은 빠르게 느껴지지만, 시간이 지나면 그 '나중'은 오지 않고 코드는 점점 더 복잡해집니다. 결국 새로운 기능을 추가할 때마다 기존 코드를 건드리기 두려워지고, 수정 대신 코드를 추가하는 방식으로 문제를 회피하게 되죠.
React 개발에서 '책임'이란?
React 개발은 결국 함수 개발이라고 할 수 있습니다. 비즈니스 로직은 말할 것도 없이 함수로 개발하고 UI조차도 함수로 개발하는 특징을 갖고 있죠. 그리고 함수가 하는 일이 곧 책임입니다. 그래서 React에서의 책임은 ‘함수가 어떤 비즈니스 로직을 수행하는가' 뿐만 아니라 ‘어떤 UI를 어떻게 그려내는가’를 포함한다고 할 수 있습니다.
React에서 단일 책임 원칙 지키기
1. 비즈니스 로직과 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를 작은 컴포넌트 단위로 쪼갤 필요는 없다는 것입니다. 책임의 단위는 각 회사마다 다르고, 개발자마다 느끼는 책임의 범위도 다릅니다. 중요한 것은 실제로 재사용되는 단위, 독립적으로 변경되는 단위를 기준으로 적합한 책임의 단위를 찾아내고 관리하는 것입니다. 모든 요소를 강박적으로 쪼갤 필요는 없습니다.
3. 비즈니스 로직 세분화
비즈니스 로직과 UI를 분리했다고 해서 끝이 아닙니다. 비즈니스 로직 자체도 여러 책임으로 나눌 수 있습니다. Custom Hook 안에서도 여러 가지 일을 한꺼번에 처리하고 있다면, 그것 역시 단일 책임 원칙을 위반하는 것입니다.
예를 들어 장바구니 기능을 개발한다고 가정해봅시다.
이 Hook은 다음과 같은 여러 책임을 가지고 있습니다
장바구니 데이터 가져오기
가격 계산 (총액, 할인, 배송비)
장바구니 아이템 관리
이렇게 되면 어떤 문제가 발생할까요?
주문 페이지에서 가격 계산만 필요한데, useCart 를 사용하면 불필요한 장바구니 데이터 fetching까지 실행됩니다
가격을 계산하는 코드는 외부 의존성이 없는 순수 함수 성격의 코드인데, 이를 테스트하기 위해서는 API mocking까지 해야합니다.
그럼 어떻게 책임단위로 분리할 수 있을까요?
로직을 분리하면 이제 필요한 곳에서 조립해 사용할 수도 있고, 각 모듈을 독립적으로 재사용 할 수도 있습니다. 테스트하기도 훨씬 수월해졌죠.
이렇게 비즈니스 로직을 적당한 책임 단위로 분리해 재사용성을 높이고 유지보수를 용이하게 만들었습니다. 비즈니스 로직도 UI 로직과 마찬가지로 억지로 작은 단위로 분리하려고 힘쓸 필요는 없습니다. 우선 적당한 책임 단위로 만들고, 책임 분리가 필요한 시점에 분리해도 괜찮습니다. 가장 중요한건 분리가 필요하다고 느끼는 시점에는 반드시 분리를 해 주는 것이라고 생각합니다.
마치며
간단하게 단일 책임 원칙이 React에 어떤식으로 적용될 수 있는지 살펴봤습니다. 사실 위에 적은 이점 외에도 책임을 분리하면 좋은 점이 하나 더 있다고 생각합니다. 바로 AI에게 일을 맡길 때 굉장히 좋은 결과물로 나올 때가 많다는 것입니다. 이것저것 여러 책임을 부여한 컴포넌트를 AI에 만들어달라고 했을 때와 책임단위로 일을 분리한 뒤 한 개씩 AI에 만들어달라고 했을 때, 후자가 훨씬 더 좋은 결과물을 만들어 낼 때가 많았습니다. AI 생성물을 관리하는 입장에서도 후자가 훨씬 유리하겠죠.
단일 책임 원칙은 객체지향의 전유물이 아닙니다. SOLID 원칙도 그렇고 다른 개발원칙도 그렇습니다. React 개발에서도, 아니 어떤 개발에서도 적용할 수 있는 철학입니다. 중요한 건 원칙을 맹목적으로 따르는 게 아니라, 그 이면의 철학을 이해하고 상황에 맞게 적용하는 것입니다.
여러분의 컴포넌트는 너무 많은 책임을 떠안고 있진 않나요? 컴포넌트가 힘들어하고 관리자도 힘들어하고 있진 않은지요? 한 번 돌아보는 시간을 가져보시면 유익한 시간이 되리라 확신합니다.
결국 좋은 설계는 원칙을 맹목적으로 따르는 것이 아니라, 원칙을 ‘이해한 뒤 선택적으로 적용하는 것’에서 시작됩니다.

