PromleeBlog
sitemap
aboutMe

posting thumbnail
React 컴포넌트 설계 원칙과 패턴으로 품질 높이기 - React, 알고 쓰자 9일차
Designing React Components Principles & Patterns for Quality - React Explained Day 9

📅

🚀

들어가기 전에 🔗

우리는 지금까지 React의 핵심 개념들을 배우고 다양한 기능들을 활용하는 방법을 익혔습니다.
이제는 단순히 '동작하는' 코드를 넘어, '잘 만들어진' 코드를 작성하는 방법에 대해 고민해 볼 시간입니다.
특히 React 개발의 핵심 단위인
컴포넌트
를 어떻게 설계하느냐는 전체 애플리케이션의 품질, 유지보수성, 확장성에 큰 영향을 미칩니다.


잘 설계된 컴포넌트는 마치 잘 만들어진 레고 블록처럼, 쉽게 이해하고 테스트할 수 있으며 다른 곳에서도 가져다 쓰기(재사용) 편리합니다.
반면, 잘못 설계된 컴포넌트는 코드를 읽기 어렵게 만들고, 작은 수정이 예상치 못한 문제를 일으키며, 협업을 힘들게 만듭니다.


오늘은 좋은 React 컴포넌트를 만들기 위한 몇 가지 중요한 설계
원칙
과 널리 사용되는
패턴
들을 함께 살펴보겠습니다.

🚀

단일 책임 원칙 (Single Responsibility Principle, SRP) 🔗

소프트웨어 설계의 기본 원칙 중 하나인
단일 책임 원칙(SRP)
은 React 컴포넌트 설계에도 매우 중요하게 적용됩니다.
SRP는 간단히 말해,
하나의 컴포넌트는 단 한 가지의 책임(역할)만 가져야 한다
는 원칙입니다.

컴포넌트의 '책임'이란 무엇일까요? 🔗

컴포넌트의 책임은 다양할 수 있습니다. 예를 들어,
SRP는 하나의 컴포넌트가 이러한 여러 책임을 동시에 떠안지 않도록 권장합니다.
만약 컴포넌트가 너무 많은 일을 하고 있다면, 그 컴포넌트는 변경될 이유가 너무 많아지고, 코드가 복잡해지며, 테스트하기 어려워집니다.

SRP 적용 방법: 관심사의 분리 🔗

SRP를 적용하는 핵심은
관심사의 분리(Separation of Concerns)
입니다.
서로 다른 책임(관심사)을 가진 코드들을 별도의 컴포넌트나 훅(Hook)으로 분리하는 것입니다.
SRP 위반 예시 (하나의 컴포넌트가 너무 많은 일을 함)
function UserProfileWithPosts({ userId }: { userId: string }) {
  const [user, setUser] = React.useState(null);
  const [posts, setPosts] = React.useState([]);
  const [loading, setLoading] = React.useState(false);
 
  React.useEffect(() => {
    // 데이터 fetching 책임
    setLoading(true);
    Promise.all([fetchUser(userId), fetchPosts(userId)])
      .then(([userData, postData]) => {
        setUser(userData);
        setPosts(postData);
      })
      .finally(() => setLoading(false));
  }, [userId]);
 
  // UI 렌더링 책임 (사용자 정보 + 게시글 목록)
  if (loading) return <div>로딩 중...</div>;
  if (!user) return <div>사용자 정보 없음</div>;
 
  return (
    <div>
      {/* 사용자 정보 렌더링 부분 */}
      <h2>{user.name}</h2>
      <p>{user.email}</p>
      <hr />
      {/* 게시글 목록 렌더링 부분 */}
      <h3>게시글</h3>
      <ul>
        {posts.map(post => <li key={post.id}>{post.title}</li>)}
      </ul>
    </div>
  );
}
SRP 적용 예시 (로직과 UI 분리)
// 데이터 fetching 로직 분리 (커스텀 훅)
function useUserData(userId: string) { /* ... user fetching 로직 ... */ return { user, loadingUser }; }
function usePostData(userId: string) { /* ... posts fetching 로직 ... */ return { posts, loadingPosts }; }
 
// UI 컴포넌트 분리
function UserInfo({ user }: { user: any }) { /* ... 사용자 정보 UI ... */ }
function PostList({ posts }: { posts: any[] }) { /* ... 게시글 목록 UI ... */ }
 
// 각 책임을 조합하는 상위 컴포넌트
function UserProfilePage({ userId }: { userId: string }) {
  const { user, loadingUser } = useUserData(userId);
  const { posts, loadingPosts } = usePostData(userId);
 
  if (loadingUser || loadingPosts) return <div>로딩 중...</div>;
  if (!user) return <div>사용자 정보 없음</div>;
 
  return (
    <div>
      <UserInfo user={user} />
      <hr />
      <PostList posts={posts} />
    </div>
  );
}
SRP 적용 전후
SRP 적용 전후

SRP의 장점 🔗

Q: "단일 책임 원칙(SRP)이란 무엇이고 React 컴포넌트에 어떻게 적용할 수 있나요?"
A: "하나의 컴포넌트는 하나의 책임만 가져야 한다는 원칙입니다. 로직은 커스텀 훅으로, UI는 더 작은 컴포넌트로 분리하여 적용할 수 있으며, 이는 테스트 용이성과 재사용성을 높입니다."

🚀

Atomic Design 패턴: 체계적인 UI 구성 🔗

Atomic Design은 UI 컴포넌트를 마치 화학의 원자, 분자, 유기체처럼 체계적인 단계로 나누어 설계하는 방법론입니다.
디자인 시스템 구축과 UI 일관성 유지에 매우 유용합니다.

5단계 구성 요소 🔗

Atomic Design은 UI를 다음 5단계의 계층 구조로 나눕니다.
  1. Atoms (원자)
    UI를 구성하는 가장 작은 기본 단위입니다. 더 이상 분해될 수 없는 HTML 요소나 기본적인 스타일링 단위입니다. (예: 버튼, 입력 필드, 레이블, 아이콘)
  2. Molecules (분자)
    여러 개의 원자가 모여 특정 기능을 수행하는 단위입니다. 원자들을 조합하여 조금 더 복잡한 UI 요소를 만듭니다. (예: 검색 폼(입력 필드 + 버튼), 네비게이션 링크(아이콘 + 레이블))
  3. Organisms (유기체)
    여러 개의 분자나 원자가 모여 더 복잡하고 독립적인 UI 섹션을 구성합니다. 페이지의 특정 영역을 나타내는 경우가 많습니다. (예: 헤더(로고 + 네비게이션 + 검색 폼), 상품 카드, 게시글 목록)
  4. Templates (템플릿)
    페이지의 전체적인 레이아웃 구조를 정의합니다. 실제 콘텐츠는 없고, 어떤 유기체와 분자들이 배치될지 보여주는 뼈대 역할을 합니다. (예: 블로그 포스트 레이아웃, 상품 목록 페이지 레이아웃)
  5. Pages (페이지)
    템플릿에 실제 콘텐츠(데이터)를 넣어 완성된 최종 페이지 모습입니다. 사용자가 실제로 보게 되는 화면입니다. (예: 특정 블로그 포스트 페이지, 특정 카테고리의 상품 목록 페이지)
Atomic Design의 5단계
Atomic Design의 5단계

Atomic Design의 장점과 고려사항 🔗

➡️

장점 🔗

➡️

고려사항 🔗


🚀

Container / Presentational 패턴: 로직과 뷰의 분리 🔗

Container/Presentational 패턴은 컴포넌트를 두 가지 종류로 나누어 관심사를 분리하는 고전적인 디자인 패턴입니다.

Container 컴포넌트 🔗

어떻게 동작하는가
에 집중합니다.

Presentational 컴포넌트 🔗

어떻게 보이는가
에 집중합니다.
Presentational 패턴 예시
import React from 'react';
 
// --- Presentational Component ---
interface TodoListProps {
  todos: { id: number; text: string; completed: boolean }[];
  onToggleTodo: (id: number) => void;
}
 
// 오직 props를 받아 UI를 그리는 역할만 수행
function TodoListUI({ todos, onToggleTodo }: TodoListProps) {
  return (
    <ul>
      {todos.map(todo => (
        <li
          key={todo.id}
          onClick={() => onToggleTodo(todo.id)}
          style={{ textDecoration: todo.completed ? 'line-through' : 'none', cursor: 'pointer' }}
        >
          {todo.text}
        </li>
      ))}
    </ul>
  );
}
Container 패턴 예시
// --- Container Component ---
import { useTodos } from './hooks/useTodos'; // 가상의 커스텀 훅 (데이터 로직 분리)
 
function TodoListContainer() {
  // 데이터 fetching 및 상태 관리 로직은 커스텀 훅 또는 여기서 직접 처리
  const { todos, toggleTodo } = useTodos(); // 가상의 훅 사용 예시
 
  // Presentational 컴포넌트에 필요한 데이터와 콜백 전달
  return <TodoListUI todos={todos} onToggleTodo={toggleTodo} />;
}
가상의 커스텀 훅 예시
// --- 가상의 커스텀 훅 예시 ---
// hooks/useTodos.ts (가상 예시)
function useTodos() {
  const [todos, setTodos] = React.useState([]);
  // ... 데이터 fetching 로직 ...
  const toggleTodo = (id: number) => { /* ... 상태 업데이트 로직 ... */ };
  return { todos, toggleTodo };
}
Container 컴포넌트와 Presentational 컴포넌트의 관계
Container 컴포넌트와 Presentational 컴포넌트의 관계

Hooks 시대의 변화와 여전히 유효한 철학 🔗

React Hooks(특히 커스텀 훅)가 등장하면서, Container 컴포넌트가 담당하던 로직 부분을 커스텀 훅으로 분리하는 것이 더 일반적이 되었습니다.
이로 인해 명시적인 Container 컴포넌트의 필요성이 줄어들기도 했습니다.
하지만 이 패턴의 핵심 철학인
관심사의 분리
(로직과 뷰의 분리)는 여전히 매우 중요합니다.
Presentational 컴포넌트처럼 순수하게 UI 렌더링에만 집중하는 컴포넌트를 만드는 것은 여전히 코드의 재사용성과 테스트 용이성을 높이는 좋은 방법입니다.
Q: "Container/Presentational 패턴이란 무엇인가요? Hooks 등장 이후 이 패턴은 어떻게 변화했나요?"
A: "로직(Container)과 뷰(Presentational)를 분리하는 패턴입니다. Hooks 등장 후 로직은 커스텀 훅으로 분리하는 경향이 있지만, 관심사 분리라는 핵심 철학은 여전히 유효하며, 순수한 UI 컴포넌트 작성은 재사용성과 테스트 용이성에 도움이 됩니다."

🚀

재사용성과 테스트 가능성 높이기 🔗

위에서 소개한 원칙과 패턴 외에도 컴포넌트의 품질을 높이기 위한 몇 가지 실용적인 팁이 있습니다.

명확한 Props 설계 🔗

컴포넌트 합성 (Composition) 활용 🔗

의존성 최소화 및 순수 컴포넌트 지향 🔗


🚀

결론 🔗

오늘은 재사용 가능하고, 테스트하기 쉬우며, 유지보수하기 좋은 React 컴포넌트를 설계하기 위한 몇 가지 중요한 원칙과 패턴을 살펴보았습니다.
프로젝트의 규모, 팀의 경험, 요구사항 등을 고려하여 적절한 방법을 선택하고 유연하게 적용하는 것이 중요합니다.
또한, 처음부터 완벽한 설계를 하려고 하기보다는, 코드를 작성하고 리팩토링하는 과정을 통해 점진적으로 개선해 나가는 것이 현실적인 접근 방식일 수 있습니다.
다음 시간에는 마이크로서비스 아키텍처의 프론트엔드 버전인 *마이크로 프론트엔드(Micro Frontend)*에 대해 알아보겠습니다.
거대한 프론트엔드 애플리케이션을 여러 팀이 독립적으로 개발하고 배포하는 방법에 대해 살펴보겠습니다.

참고 🔗