🧐 TL;DR
- 단일 책임 원칙(SRP): 한 컴포넌트(또는 함수)는 한 가지 역할만 해야 한다
- SRP 위반 시 문제점: UI, 상태 관리, 비즈니스 로직이 섞이면 유지보수 어렵고, 재사용성이 낮아진다
- SRP를 적용하면 리액트 코드가 더 깔끔해지고 유지보수가 쉬워진다!
리액트에서 단일 책임 원칙(Single Responsibility Principle, SRP)을 적용하면 컴포넌트의 유지보수성, 재사용성, 테스트 용이성을 크게 향상시킬 수 있습니다. 이 글에서는 SRP의 개념과 리액트에서 이를 적용하는 방법을 실제 코드 예제와 함께 알아보겠습니다.
1. 단일 책임 원칙(SRP)이란?
단일 책임 원칙(SRP)은 로버트 C. 마틴이 제안한 SOLID 원칙 중 하나로, 클래스나 모듈은 하나의 변경 이유만 가져야 한다는 것을 의미합니다. 즉, 하나의 클래스나 모듈이 여러 가지 기능을 수행하지 않고, 단 하나의 기능만을 책임져야 함을 강조합니다.
2. 리액트에서의 SRP 적용
리액트에서는 컴포넌트를 설계할 때 SRP를 적용하여 각 컴포넌트가 하나의 책임만을 가지도록 분리하는 것이 중요합니다. 예를 들어, 데이터 페칭, 상태 관리, UI 렌더링 등을 하나의 컴포넌트에 모두 포함시키는 대신, 이를 각각의 컴포넌트나 훅으로 분리할 수 있습니다.
예시
아래는 할 일 목록(Todo List) 애플리케이션 코드입니다.
const TodoApp = () => {
const [todos, setTodos] = useState([]);
const [input, setInput] = useState("");
const addTodo = () => {
if (!input.trim()) return;
setTodos([...todos, { id: Date.now(), text: input, completed: false }]);
setInput("");
};
const toggleTodo = (id) => {
setTodos(todos.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
));
};
const removeTodo = (id) => {
setTodos(todos.filter(todo => todo.id !== id));
};
return (
<div>
<h1>Todo List</h1>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Enter a task"
/>
<button onClick={addTodo}>Add</button>
<ul>
{todos.map(todo => (
<li key={todo.id}>
<span
style={{ textDecoration: todo.completed ? "line-through" : "none" }}
onClick={() => toggleTodo(todo.id)}
>
{todo.text}
</span>
<button onClick={() => removeTodo(todo.id)}>Delete</button>
</li>
))}
</ul>
</div>
);
};
문제점
- UI와 상태 관리가 섞여 있음: 유지보수 어려움
- 변경 이유가 많음: 데이터 관리, UI 처리, 입력 처리를 모두 담당
- 재사용성이 낮음: 특정 기능을 분리하여 활용하기 어려움
SRP를 적용하여 개선하기
SRP를 적용하면 각 역할을 별도의 컴포넌트와 훅으로 분리하여 유지보수성과 확장성을 높일 수 있습니다.
1) 할 일 목록을 관리하는 커스텀 훅 (useTodos
)
const useTodos = () => {
const [todos, setTodos] = useState([]);
const addTodo = (text) => {
if (!text.trim()) return;
setTodos([...todos, { id: Date.now(), text, completed: false }]);
};
const toggleTodo = (id) => {
setTodos(todos.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
));
};
const removeTodo = (id) => {
setTodos(todos.filter(todo => todo.id !== id));
};
return { todos, addTodo, toggleTodo, removeTodo };
};
- 상태 관리와 로직을 useTodos
로 분리: UI와 분리되어 깔끔해짐
2) 입력 필드 컴포넌트 (TodoInput
)
const TodoInput = ({ onAdd }) => {
const [input, setInput] = useState("");
const handleSubmit = () => {
onAdd(input);
setInput("");
};
return (
<div>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Enter a task"
/>
<button onClick={handleSubmit}>Add</button>
</div>
);
};
- 입력 기능을 TodoInput
으로 분리: 재사용 가능
3) 개별 할 일 항목 컴포넌트 (TodoItem
)
const TodoItem = ({ todo, onToggle, onDelete }) => {
return (
<li>
<span
style={{ textDecoration: todo.completed ? "line-through" : "none" }}
onClick={() => onToggle(todo.id)}
>
{todo.text}
</span>
<button onClick={() => onDelete(todo.id)}>Delete</button>
</li>
);
};
- 할 일 항목 표시를 TodoItem
으로 분리: UI와 로직 분리
4) 할 일 목록을 표시하는 컴포넌트 (TodoList
)
const TodoList = ({ todos, onToggle, onDelete }) => {
return (
<ul>
{todos.map(todo => (
<TodoItem key={todo.id} todo={todo} onToggle={onToggle} onDelete={onDelete} />
))}
</ul>
);
};
- 목록 렌더링을 TodoList
컴포넌트로 분리
5) TodoApp
에서 조립
const TodoApp = () => {
const { todos, addTodo, toggleTodo, removeTodo } = useTodos();
return (
<div>
<h1>Todo List</h1>
<TodoInput onAdd={addTodo} />
<TodoList todos={todos} onToggle={toggleTodo} onDelete={removeTodo} />
</div>
);
};
- 각 기능이 독립적인 컴포넌트로 분리됨: 가독성 향상, 유지보수 쉬움
3. SRP 적용의 이점
- 유지보수 용이: 각 기능이 별도의 컴포넌트로 분리되어 있어 수정이 쉬움
- 재사용 가능: TodoInput
, TodoList
, TodoItem
은 다른 프로젝트에서도 활용 가능
- 가독성 향상: 코드가 한눈에 이해되기 쉬우며, 각 컴포넌트의 역할이 명확함
- 테스트 용이: 개별 컴포넌트를 독립적으로 테스트할 수 있어 테스트 코드 작성이 쉬워짐
4. 정리
단일 책임 원칙(SRP)을 적용하면 코드가 더 깔끔해지고 유지보수가 쉬워지며, 확장성이 증가합니다.
리액트에서 SRP를 적용할 때는 훅(Hooks)과 컴포넌트를 적절히 분리하는 것이 핵심입니다.
이제 SRP를 고려하여 리액트 애플리케이션을 설계해 보세요!
'Learning > NOTE' 카테고리의 다른 글
Barrel Pattern(배럴 패턴) (0) | 2025.03.24 |
---|---|
[FE] Create React App(CRA) 지원 종료 (1) | 2025.02.27 |