2편 React는 왜 컴포넌트 기반을 택했을까?

JIIJIIJ·2025년 9월 14일
0

React

목록 보기
31/35
post-thumbnail

들어가며 — 1편에서 이어가기

1편에서는 React가 “선언적”이라는 철학을 다뤘다.
UI를 상태의 함수로 서술할 수 있다는 단순한 공식 UI = f(state)를 통해, 우리는 UI를 더 직관적으로 바라볼 수 있었다.

이번 글에서는 그 선언적 철학이 “컴포넌트”라는 단위를 중심으로 어떻게 구현되는지를 살펴본다. React는 단순히 선언적일 뿐 아니라, 컴포넌트 기반(Component-based) 아키텍처를 지향한다.

그런데 왜 “컴포넌트”일까? 이것도 React 창시자들이 겪었던 구체적인 문제에서 출발했다.


문제 제기 — 기술 단위로 쪼개던 과거의 한계

전통적인 웹 개발은 HTML, CSS, JavaScript를 기술별로 분리해서 다뤘다. 이는 “관심사의 분리”라는 소프트웨어 개발 원칙에 따른 것이었다.

  • HTML: 구조 (Structure)
  • CSS: 스타일 (Presentation)
  • JavaScript: 동작 (Behavior)

표면적으로는 깔끔한 분리처럼 보이지만, 실제 개발에서는 문제가 있었다.

전통적 웹 개발 방식에서 일반적으로 발생하는 문제들

대규모 웹 애플리케이션 개발에서 기술별 분리가 야기하는 일반적인 문제들은 다음과 같다:

1. 기능 하나를 수정하려면 여러 파일을 찾아다녀야 했다

  • 버튼 하나의 텍스트를 바꾸려고 해도 HTML 파일에서 구조를 찾고, CSS 파일에서 스타일을 찾고, JS 파일에서 이벤트 핸들러를 찾아야 했다.

2. 컴포넌트 재사용이 어려웠다

  • 같은 버튼을 다른 페이지에서 사용하려면 HTML 마크업, CSS 클래스, JavaScript 코드를 모두 복사해야 했다.

3. 팀 협업에서 충돌이 빈번했다

  • 여러 개발자가 같은 CSS 파일이나 JS 파일을 동시에 수정하면 충돌이 발생했다.

즉, 사용자가 경험하는 단위(UI 기능)개발자가 다루는 단위(기술별 파일)가 완전히 어긋나 있었다.

⚠️ 주의
HTML/CSS/JS 분리는 웹 표준 원칙에 따른 것이지만, 규모가 커질수록 한계를 드러냈고, React가 이를 다른 방식으로 재해석했다고 보는 것이 적절합니다.


철학적 배경: “관심사의 분리” 혁명

Pete Hunt의 혁신적 통찰

React 초기 개발자인 Pete Hunt는 2013년 JSConf에서 개발 업계를 뒤흔든 발언을 했다:

“We’re not separating concerns, we’re separating technologies. And that’s not the same thing.”

“우리는 관심사를 분리하는 게 아니라 기술을 분리하고 있었다. 그리고 그것은 같은 것이 아니다.”

전통적 사고: HTML, CSS, JS로 분리 (기술별)

React의 사고: 버튼, 폼, 카드로 분리 (기능별)

📖 참고 | Pete Hunt: React: Rethinking Best Practices


한 프로그래머의 영감 — XHP에서 React까지

Facebook의 XHP와 초기 아이디어

Facebook은 2010년경에 XHP라는 PHP 확장을 사용하기 시작했다. XHP는 PHP 코드 안에 XML-유사한 마크업을 안전하게 포함할 수 있게 해주는 기술이다. 이 방식은 HTML, 스타일, 동작이 분리된 전통적 패턴보다는 마크업과 로직이 더 응집된 구조를 허용했다.

이 아이디어는 “마크업을 코드 내부에 포함시키는 것”이 UI 구조를 더 명확하고 재사용 가능하게 할 수 있다는 영감으로 작동했다. 특히 XHP가 만들어낸 컴포넌트처럼 사용할 수 있는 UI 요소 개념은 이후 React 및 JSX 개발에 영향을 주었다.

📖 참고 | React Blog: Our First 50,000 Stars


FaxJS에서 React로 이어지는 흐름

Jordan Walke는 XHP 등에서 받은 영감을 바탕으로 2011년에 FaxJS라는 초기 실험 프로젝트를 시작했다. FaxJS는 BoltJS 내부의 한계를 개선하려는 시도로, React의 여러 핵심 개념인 props, state, 렌더 트리의 재평가, 서버 사이드 렌더링 가능성, 컴포넌트라는 재사용 가능한 UI 블록이 FaxJS 단계에서 대부분 제안되었다.

이러한 실험들이 점차 Facebook 내부에 통합되고, BoltJS에서 기능적 API 중심으로 전환되는 과정에서 FaxJS → FBolt → React로 이름이 바뀌면서 발전했다.

📖 참고 | React Blog: Our First 50,000 Stars

⚠️ 주의
“FBolt”라는 이름은 일부 발표나 글에서만 언급되며, 공식적으로 널리 사용된 이름은 아닙니다.


새로운 프레임워크: 경계/핵심/연결 모델

Jordan Walke가 FaxJS에서 React로 발전시킨 아이디어를 체계화하면, 컴포넌트는 세 가지 관점에서 이해할 수 있다:

경계(Boundary) - “내가 책임지는 영역이 어디까지인가?”

"좋아요 버튼이 안 돼요" → 누구 책임?
- HTML 개발자: "마크업은 문제없어요"
- CSS 개발자: "스타일도 정상이에요"  
- JS 개발자: "이벤트도 잘 작동해요"

React 컴포넌트는 명확한 경계를 갖는다. 예를 들어 LikeButton이라면,

버튼의 상태와 UI 업데이트 책임이 해당 컴포넌트 내부에 응집된다.

즉, “좋아요 버튼이 안 된다”면 LikeButton 하나만 보면 된다.

📖 참고 |
React Blog: Our First 50,000 Stars
React 공식 문서 – Passing Props to a Component


핵심(Core) - “이 컴포넌트의 본질은 무엇인가?”

각 컴포넌트는 하나의 명확한 목적을 가진다. Unix 철학의 “Do one thing well”과 같은 원리다.

// ❌ 나쁜 예: 여러 책임이 섞임
function UserProfileAndNavigationAndSettings({ user }) {
  return (
    <div>
      <nav>{/* 네비게이션 */}</nav>
      <div>{/* 프로필 */}</div>
      <div>{/* 설정 */}</div>
    </div>
  );
}

// ✅ 좋은 예: 각자의 핵심에 집중
function UserProfile({ user }) {
  return <div>{user.name}</div>; // 프로필 표시가 핵심
}

function Navigation({ items }) {
  return <nav>{/* 네비게이션이 핵심 */}</nav>;
}

📖 참고 |
React Blog: Our First 50,000 Stars
React 공식 문서 – Composition vs Inheritance


연결(Connection) - “다른 컴포넌트와 어떻게 소통하는가?”

<Layout>
  <Header user={currentUser} />
  <Content>
    <UserProfile user={currentUser} />
  </Content>
  <Footer />
</Layout>

📖 참고 |
React 공식 문서 – Passing Props to a Component
props.children 및 Composition 권장


일상생활로 이해하기

집 짓기 비유

전통적 방식 (기술별 분리):

  • 벽돌팀: "우리는 벽돌만 쌓습니다"
  • 배관팀: "우리는 배관만 설치합니다"
  • 전기팀: "우리는 전선만 연결합니다"

각 팀은 자신의 전문 분야에만 집중하지만, **"욕실을 만든다"**는 하나의 목표를 달성하려면 세 팀이 복잡하게 조율해야 한다.

React 방식 (기능별 분리):

  • 욕실팀: "욕실에 필요한 벽돌, 배관, 전기를 모두 책임집니다"
  • 주방팀: "주방에 필요한 모든 것을 책임집니다"
  • 거실팀: "거실에 필요한 모든 것을 책임집니다"

각 팀이 기능 단위로 완전한 책임을 진다. 욕실에 문제가 생기면 욕실팀만 찾으면 되고, 다른 집에서도 욕실팀의 설계를 그대로 재사용할 수 있다.

코드로 보는 차이점

전통적 방식: 명령형 + 기술별 분리

// HTML 
<button id="like-button" class="btn btn-like">
  <span class="like-count">0</span>
  <span class="like-text">좋아요</span>
</button>


// CSS
.btn-like { background: white; border: 1px solid #ccc; }
.btn-like.liked { background: #ff6b6b; color: white; }


// JavaScript
const button = document.getElementById('like-button');
let count = 0, isLiked = false;

button.addEventListener('click', () => {
  count += isLiked ? -1 : 1;
  isLiked = !isLiked;
  // DOM 직접 조작...
});

React 방식: 선언적 + 컴포넌트 기반

function LikeButton() {
  const [count, setCount] = useState(0);
  const [isLiked, setIsLiked] = useState(false);

  const handleClick = () => {
    setCount(prev => isLiked ? prev - 1 : prev + 1);
    setIsLiked(!isLiked);
  };

  return (
    <button 
      className={`btn btn-like ${isLiked ? 'liked' : ''}`}
      onClick={handleClick}
    >
      <span className="like-count">{count}</span>
      <span className="like-text">
        {isLiked ? '좋아요 취소' : '좋아요'}
      </span>
    </button>
  );
}

React 방식의 핵심 장점:

  • 모든 관련 코드가 한 곳에 모여 있음
  • 상태가 바뀌면 UI가 자동으로 업데이트됨
  • 으로 어디서든 재사용 가능

합성의 무한한 가능성

React 컴포넌트가 진정 혁신적인 이유는 무한한 조합 가능성 때문이다.

// 기본 블록들
function Button({ variant, children, ...props }) {
  return <button className={`btn btn-${variant}`} {...props}>{children}</button>;
}

function Icon({ name }) {
  return <i className={`icon-${name}`} />;
}

// 무한한 조합들
<Button variant="primary">기본 버튼</Button>

<Button variant="primary">
  <Icon name="heart" />
  좋아요
</Button>

상속의 한계 vs 합성의 자유

// ❌ 상속 방식: 새로운 조합마다 클래스 필요
class PrimaryIconButton extends ??? // 다중 상속 불가능!

// ✅ 합성 방식: 자유로운 조합
<Button variant="primary">
  <Icon name="heart" />
  좋아요
</Button>

React는 처음부터 “Composition over Inheritance” 원칙을 강력하게 지지했다.

📖 참고 | React 공식 문서: Composition vs Inheritance


함수형 프로그래밍과의 연결

Jordan Walke는 처음부터 React 컴포넌트를 함수로 생각했다.

// 컴포넌트는 함수다
function UserProfile(props) {
  return <div>Hello, {props.name}!</div>;
}

// 수학 함수와 같은 원리
// UserProfile({name: "김철수"}) = <div>Hello, 김철수!</div>

이는 함수형 프로그래밍의 핵심 원칙과 일치한다:

  • 순수성: 같은 입력(props)에 대해 같은 출력(JSX)
  • 합성: 작은 함수들을 조합해서 복잡한 기능 구현
  • 재사용성: 한 번 정의하면 여러 곳에서 사용 가능

📖 참고 | Functional Programming Principles in React

⚠️ 주의
React 컴포넌트는 항상 순수하지 않으며, “순수 함수에 가깝게 작성하고 효과는 분리한다”는 철학에 가깝습니다.


React 컴포넌트의 진짜 가치

1. 예측 가능성과 격리성

function UserCard({ user }) {
  return (
    <div className="user-card">
      <img src={user.avatar} alt={user.name} />
      <h3>{user.name}</h3>
      <p>{user.email}</p>
    </div>
  );
}

같은 props를 주면 항상 같은 UI가 나온다. 그리고 한 컴포넌트의 문제가 다른 컴포넌트에 전파되지 않는다. 이는 디버깅과 테스트를 매우 쉽게 만들 뿐만 아니라, 대규모 팀에서 안전하게 개발할 수 있게 해준다.

2. 팀 협업의 혁신

기술별 분리에서는 “이 버튼을 수정해줘”라고 하면 HTML, CSS, JS 파일을 모두 찾아다녀야 했다. 서로 다른 파일을 동시에 수정하다 충돌도 빈번했다.

컴포넌트 기반에서는 Button.jsx 파일 하나만 수정하면 된다. 각자 다른 컴포넌트를 작업하므로 충돌이 최소화되고, 컴포넌트 단위로 명확하게 책임을 분담할 수 있다.


실제 사례: Facebook이 보여준 가능성

React 도입 후 Facebook에서 벌어진 변화는 놀라웠다. 2017년 Meta는 “메인 웹 저장소에만 3만 개가 넘는 React 컴포넌트를 보유”하고 있다고 발표했다. 이는 기능 단위로 캡슐화된 컴포넌트를 대규모로 운영할 수 있음을 실증했다.

2020년 Facebook.com을 전면 개편할 때도 React와 Relay를 중심으로 아키텍처를 재설계했다. 목표는 성능과 유지보수성, 향후 확장성이었다.

📖 참고 |
Engineering at Meta: React 16
Facebook.com Redesign

⚠️ 주의
Facebook.com 개편 당시 성능 개선 수치 같은 정량적 데이터는 공개되지 않았습니다. 다만 공식 엔지니어링 블로그를 통해 React와 Relay 중심으로 아키텍처를 재설계했다는 사실과, 그 목적이 성능·유지보수성·확장성에 있었다는 점은 확인할 수 있습니다.


한계와 트레이드오프

물론 컴포넌트 기반 접근에도 한계는 있다. React의 성공이 때로는 과도한 컴포넌트 분할로 이어지기도 한다. 모든 것을 컴포넌트로 만들려다 보면 런타임 오버헤드가 증가하고, 번들 크기가 커지며, 메모리 사용량도 늘어날 수 있다.

또한 지나친 분할은 props drilling불필요한 렌더링을 유발해, 오히려 유지보수를 어렵게 만들 수 있다.

// ✅ 적절한 수준의 분할
function UserCard({ user }) {
  return (
    <div className="user-card">
      <Avatar src={user.avatar} alt={user.name} />
      <div className="user-info">
        <h3>{user.name}</h3>
        <p>{user.email}</p>
      </div>
      <ActionButtons user={user} />
    </div>
  );
}

핵심은 균형점을 찾는 것이다. 너무 세분화하지도, 너무 큰 덩어리로 만들지도 않는 적절한 크기의 컴포넌트를 만드는 것이 중요하다.


미래 전망

React가 제시한 컴포넌트 기반 사고는 이미 웹을 넘어 확산되고 있다. Figma의 Component 시스템, Sketch의 Symbol 라이브러리, Adobe XD의 Component States는 모두 React의 영향을 받은 것이다.

개발 프로세스도 변하고 있다. 컴포넌트 주도 개발(Component-Driven Development)이 널리 퍼지고, Design System을 중심으로 한 팀 협업이 표준이 되고 있다. 재사용 가능한 컴포넌트 라이브러리는 이제 회사의 핵심 자산이 되었다.


마무리

React가 “컴포넌트 기반”이라는 말은 단순한 코드 구조화 기법이 아니다. 이는 UI를 어떻게 사고하고 조직할 것인가에 대한 근본적인 철학 변화였다.

기술 분리에서 관심사 분리로.

상속 기반 확장에서 합성 기반 조합으로.

Jordan Walke가 XHP에서 영감받아 시작한 실험은 결국 전체 업계의 패러다임을 바꿨다. React 컴포넌트는 이제 수백만 개발자의 일상이 되었고, 웹을 넘어 모바일, 데스크톱까지 영향을 미치고 있다.

컴포넌트 기반 사고는 “복잡성을 인간이 이해할 수 있는 단위로 나누고, 그것들을 예측 가능한 방식으로 조합한다”는 소프트웨어 공학의 기본 원칙을 UI 개발에 적용한 결과다. 그리고 이것이 React가 단순한 라이브러리를 넘어 현대 개발자들의 사고방식을 바꾼 이유다.

다음 편에서는 이러한 컴포넌트 기반 사고가 어떻게 실제 개발에서 구현되는지 살펴볼 것이다. Select나 Modal 같은 복잡한 UI를 여러 부품으로 나누어 조합하는 Compound Pattern부터, 로직과 UI를 철저히 분리하는 Headless 설계, 그리고 하나의 컴포넌트로 버튼도 링크도 될 수 있는 Polymorphic 패턴까지. Jordan Walke가 XHP에서 시작한 컴포넌트 철학이 어떻게 현대적인 설계 패턴으로 꽃피우는지 확인해보자.

profile
다크모드가 보기 좋아요

0개의 댓글