Dev Thinking
32완료

모듈화의 완성 - 컴포넌트 파일 구조와 Import/Export

2025-08-10
9분 읽기

리액트 공식문서 기반의 리액트 입문기

들어가며

개발을 하다 보면 코드의 양이 늘어나고 복잡해지는 것은 피할 수 없는 일입니다. 이러한 상황에서 우리는 코드를 효율적으로 관리하고 재사용하며, 여러 개발자가 협업할 수 있는 구조를 만들기 위해 노력해 왔습니다. 이러한 노력의 중심에는 '모듈화'라는 개념이 자리 잡고 있습니다. 모듈화는 코드를 기능별로 분리하여 독립적인 파일로 만들고, 필요할 때 가져다 쓰는 방식을 의미합니다. 이는 전역 스코프 오염을 방지하고 코드의 유지보수성을 크게 향상시키는 중요한 개발 기법입니다.

바닐라 자바스크립트 환경에서도 ES6 import, export 문법이 도입되면서 모듈화가 가능해졌습니다. 하지만 리액트에서는 이 모듈 시스템을 단순한 코드 분리를 넘어, 컴포넌트 기반 아키텍처를 구축하는 핵심적인 도구로 활용하고 있습니다. 모든 UI 조각을 독립적인 컴포넌트(모듈)로 만들고, 이를 조립하여 복잡한 애플리케이션을 만들어 나가는 것이죠.

이번 편에서는 자바스크립트에서 모듈화를 어떻게 다루어 왔고, 리액트에서는 이러한 모듈 시스템을 어떤 철학과 방식으로 활용하여 UI를 구성하고 있는지 함께 탐구해보고자 합니다. 단순히 문법을 아는 것을 넘어, 각 패러다임에서 모듈화가 가지는 의미와 그로 인해 변화된 개발 경험에 대해 이야기 나누는 시간이 될 것이라고 생각합니다.

UI 트리를 구성하는 모듈로서의 컴포넌트

자바스크립트의 importexport는 단순히 코드를 분리하는 문법이지만, 리액트에서는 이 모듈 시스템이 UI 트리를 구성하는 핵심적인 메커니즘으로 작용합니다. 모든 리액트 컴포넌트는 재사용 가능한 독립적인 UI 조각이자 하나의 모듈입니다. 우리는 이 컴포넌트 모듈들을 import하여 다른 컴포넌트에 포함시키고, 마치 레고 블록을 조립하듯이 UI를 계층적으로 구축해 나갑니다. 이는 바닐라 자바스크립트에서 HTML, CSS, JavaScript 파일을 각각 분리하고, 스크립트 로딩 순서나 전역 변수 관리에 신경 써야 했던 것과는 근본적으로 다른 접근 방식이라고 할 수 있습니다.

리액트 컴포넌트가 모듈화될 때 얻는 이점은 다음과 같습니다.

  • 강력한 재사용성: 한 번 만든 컴포넌트는 여러 곳에서 재사용될 수 있으며, 이는 개발 시간을 단축하고 일관된 UI를 유지하는 데 큰 도움이 됩니다.
  • 유지보수 용이성: 각 컴포넌트가 독립적이므로, 특정 기능에 문제가 발생하더라도 해당 컴포넌트만 수정하면 되어 유지보수가 훨씬 쉬워집니다.
  • 명확한 관심사 분리: 컴포넌트 하나가 하나의 UI와 그 로직을 담당하므로, 관심사 분리가 자연스럽게 이루어집니다.
  • 협업 효율성 증대: 여러 개발자가 동시에 다른 컴포넌트를 작업할 수 있어 협업 효율이 높아집니다.

이러한 컴포넌트 기반 모듈화는 리액트가 강력한 UI 개발 라이브러리로서 자리매김하는 데 결정적인 역할을 했습니다. 이제 리액트가 아닌 바닐라 자바스크립트 환경에서 어떻게 모듈화를 다루었는지 먼저 살펴보겠습니다.

자바스크립트로 유틸리티 모듈 만들기

과거 바닐라 자바스크립트 환경에서는 코드를 모듈화하고 재사용하기 위해 다양한 방법을 사용해왔습니다. IIFE(즉시 실행 함수)를 사용하여 스코프를 격리하거나, 여러 개의 <script> 태그를 사용하면서 로딩 순서에 매우 민감하게 의존하는 방식 등이 대표적이었습니다. 이러한 방식들은 전역 스코프 오염을 줄이고 코드 재사용성을 높이려는 노력이었지만, 모듈 간의 명시적인 의존성 관리나 번들링 등의 개념은 부재했습니다.

하지만 ES6(ECMAScript 2015)에서 importexport 문법이 표준으로 도입되면서 자바스크립트 자체적으로 모듈 시스템을 지원하게 되었습니다. 이제 우리는 간단한 유틸리티 함수나 상수 등을 별도의 파일로 분리하여 모듈로 만들고, 필요한 곳에서 가져다 쓸 수 있게 되었습니다.

여기서는 두 개의 숫자를 더하고 빼는 간단한 유틸리티 함수를 모듈로 만들고, 이를 HTML 파일에서 사용하는 예시를 살펴보겠습니다.

mathUtils.js 파일 (모듈)

// mathUtils.js
export function add(a, b) {
  return a + b;
}
 
export function subtract(a, b) {
  return a - b;
}
 
export const PI = 3.14159;

mathUtils.js 파일에서는 add 함수, subtract 함수, 그리고 PI 상수를 export 키워드를 사용하여 외부로 공개하고 있습니다. 이렇게 export된 요소들은 다른 자바스크립트 파일에서 import하여 사용할 수 있게 됩니다.

index.html 파일

<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>바닐라 자바스크립트 모듈 예제</title>
  </head>
  <body>
    <h1>바닐라 자바스크립트 모듈화</h1>
    <p>두 숫자의 합: <span id="sumResult"></span></p>
    <p>두 숫자의 차: <span id="subtractResult"></span></p>
    <p>파이 값: <span id="piValue"></span></p>
 
    <script type="module">
      import { add, subtract, PI } from "./mathUtils.js";
 
      document.getElementById("sumResult").textContent = add(10, 5);
      document.getElementById("subtractResult").textContent = subtract(10, 5);
      document.getElementById("piValue").textContent = PI;
    </script>
  </body>
</html>

index.html에서는 <script type="module"> 속성을 통해 해당 스크립트가 모듈임을 명시했습니다. 이러한 설정을 통해 mathUtils.js 파일에서 내보낸(export) add, subtract, PI와 같은 요소들을 import 문으로 가져와 활용할 수 있습니다. 바닐라 자바스크립트 환경에서 ES6 모듈을 사용할 때, 파일의 상대 경로와 .js 확장자를 명시하는 것이 중요한데, 그 이유에 대해서는 바로 이어서 살펴보겠습니다.

자바스크립트 방식의 특징

  1. 명시적인 파일 경로: import 시 파일의 상대 경로와 확장자를 명확하게 명시해야 합니다. 이는 브라우저가 직접 모듈을 로드하기 때문에 모듈의 정확한 위치를 알려주어야 하기 때문입니다.
  2. 직접적인 브라우저 지원: ES6 모듈은 <script type="module">을 통해 현대 브라우저에서 직접 지원됩니다. 별도의 빌드 과정 없이 모듈 기능을 사용할 수 있습니다.
  3. 코드 재사용성의 향상: 함수나 상수 등을 모듈로 분리하여 여러 파일에서 재사용할 수 있게 되어, 전역 스코프 오염을 줄이고 코드 관리를 용이하게 합니다.
  4. 제한적인 의존성 관리: 모듈 간의 의존성은 개발자가 직접 관리해야 하며, 복잡한 프로젝트에서는 의존성 트리(dependency tree)가 복잡해지거나 순환 참조 등의 문제가 발생할 수 있습니다.
  5. 번들링의 필요성: 프로덕션 환경에서는 모듈 파일이 많아지면 네트워크 요청이 증가하여 성능 저하로 이어질 수 있습니다. 이를 해결하기 위해 웹팩(Webpack)이나 롤업(Rollup)과 같은 번들러(bundler)를 사용해야 할 필요가 있습니다. 이는 type="module" 스크립트를 사용해도 마찬가지입니다.

리액트로 컴포넌트 모듈 만들기

리액트에서는 ES6 모듈 시스템을 컴포넌트 기반 개발의 핵심으로 사용합니다. 모든 컴포넌트가 독립적인 모듈이며, 이들을 import하고 export하여 애플리케이션의 UI 트리를 구성합니다. 리액트 개발 환경(Next.js, Vite 등)은 이러한 모듈화 과정을 개발자가 크게 신경 쓰지 않아도 되도록 빌드 도구를 통해 자동화해 줍니다. 따라서 우리는 파일 경로와 확장자를 일일이 명시하는 대신, 컴포넌트 이름만으로 깔끔하게 모듈을 가져올 수 있습니다.

여기서는 앞서 자바스크립트 예제에서 살펴본 덧셈/뺄셈 기능을 가진 Calculator 컴포넌트를 만들어보겠습니다. 이 Calculator 컴포넌트가 다른 컴포넌트를 import하여 사용하는 구조를 통해 리액트에서의 모듈화가 어떻게 이루어지는지 살펴보겠습니다.

파일 구조

src/
├── components/
│   ├── Button.jsx
│   └── Calculator.jsx
└── App.jsx
└── main.jsx

src/components/Button.jsx (버튼 컴포넌트 모듈)

// src/components/Button.jsx
export default function Button({ onClick, children }) {
  return <button onClick={onClick}>{children}</button>;
}

Button.jsx 파일은 재사용 가능한 버튼 컴포넌트를 정의하고 export default로 내보냅니다. 이렇게 하면 다른 파일에서 import Button from './Button';과 같이 이름을 사용하여 가져올 수 있습니다.

src/components/Calculator.jsx (계산기 컴포넌트 모듈)

// src/components/Calculator.jsx
import { useState } from 'react';
import Button from './Button'; // Button 컴포넌트 import
 
export default function Calculator() {
  const [result, setResult] = useState(0);
 
  const handleAdd = () => {
    setResult(prevResult => prevResult + 5);
  };
 
  const handleSubtract = () => {
    setResult(prevResult => prevResult - 5);
  };
 
  return (
    <div>
      <h2>간단 계산기</h2>
      <p>현재 값: {result}</p>
      <Button onClick={handleAdd}>5 더하기</Button>
      <Button onClick={handleSubtract}>5 빼기</Button>
    </div>
  );
}

Calculator.jsx 파일에서는 앞서 정의한 Button 컴포넌트를 import하여 사용하고 있습니다. 주목할 점은 Button 컴포넌트를 가져올 때 확장자인 .jsx를 생략할 수 있다는 것입니다. 이는 리액트 개발 환경에서 사용되는 번들러(예: Vite, Webpack)가 자동으로 파일 확장자를 유추하고 경로를 처리해주기 때문입니다. 이처럼 리액트에서는 모든 UI 조각을 컴포넌트라는 모듈로 분리하고, 이들을 조합하여 애플리케이션을 구축하는 방식이 매우 자연스럽고 강력하게 지원됩니다.

src/App.jsx (최상위 앱 컴포넌트)

// src/App.jsx
import Calculator from './components/Calculator'; // Calculator 컴포넌트 import
 
export default function App() {
  return (
    <div>
      <h1>리액트 모듈화 예제</h1>
      <Calculator />
    </div>
  );
}

App.jsx 파일은 최상위 컴포넌트로, Calculator 컴포넌트를 import하여 화면에 렌더링합니다.

src/main.jsx (진입점 파일)

// src/main.jsx
import ReactDOM from 'react-dom/client';
import App from './App'; // App 컴포넌트 import
 
ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

main.jsx는 애플리케이션의 진입점 파일로, App 컴포넌트를 가져와 HTML 문서의 root 엘리먼트에 렌더링합니다.

리액트 방식의 특징 (모듈화의 완성)

  • 1. 컴포넌트 중심의 모듈화와 UI 트리 구성: 리액트에서는 모든 UI 요소가 재사용 가능한 독립적인 **컴포넌트(모듈)**로 정의됩니다. 각 컴포넌트는 자신이 렌더링할 UI 조각과 그에 필요한 로직(State, Props, 이벤트 핸들러)을 캡슐화하며, 이를 export하고 import하여 전체 UI를 계층적인 UI 트리로 구축합니다. 이는 마치 레고 블록을 조립하듯이 애플리케이션을 만들어 나가는 방식으로, 자바스크립트의 단순한 코드 분리를 넘어 UI 아키텍처를 체계적으로 설계하는 리액트의 핵심 철학입니다. 이러한 접근 방식은 코드의 응집도를 높이고, 복잡한 UI를 관리 가능한 작은 단위로 분해하는 데 결정적인 역할을 합니다.
  • 2. 빌드 도구를 통한 개발 편의성 극대화: Next.js나 Vite와 같은 현대적인 리액트 개발 환경은 웹팩(Webpack)이나 롤업(Rollup)과 같은 번들러(Bundler)를 내장하고 있어, import 시 파일 확장자(.jsx, .js, .ts, .tsx 등)를 생략하거나 별칭 경로(alias paths)를 사용하는 등 모듈 의존성 관리를 자동으로 처리해 줍니다. 이는 개발자가 파일 경로와 관련된 번거로움 없이 오직 컴포넌트의 기능과 로직에만 집중할 수 있게 하여 개발 생산성을 크게 향상시킵니다. 개발 환경의 이러한 추상화는 복잡한 모듈 시스템을 개발자에게 더욱 친숙하게 만들어 줍니다.
  • 3. 명시적이고 간결한 의존성 관리: 리액트의 import/export 시스템은 필요한 컴포넌트나 함수를 정확히 명시적으로 가져와 사용하도록 강제합니다. 예를 들어 import Button from "./Button"과 같이 Button 컴포넌트가 어디서 왔고, 어떤 역할을 하는지 코드를 통해 명확하게 파악할 수 있습니다. 이러한 명시적인 의존성 관리는 코드의 가독성을 높이고, 특정 컴포넌트가 어떤 다른 컴포넌트에 의존하는지 한눈에 파악할 수 있게 하여 복잡한 애플리케이션의 유지보수성을 크게 향상시킵니다.
  • 4. 재사용성, 유지보수성, 협업 효율성 증대: 각 컴포넌트 모듈은 독립적으로 존재하며 자체적인 기능과 UI를 가지므로, 코드의 재사용성이 극대화됩니다. 한 번 잘 만들어진 컴포넌트는 애플리케이션의 여러 곳에서 활용될 수 있으며, 이는 개발 시간을 단축하고 UI의 일관성을 유지하는 데 기여합니다. 또한, 특정 기능 변경 시 해당 컴포넌트만 수정하면 되므로 유지보수가 훨씬 쉬워지며, 여러 개발자가 동시에 다른 컴포넌트를 작업할 수 있어 협업 효율성을 크게 높입니다.
  • 5. 선언형 UI 구축의 핵심 기반: 모듈화된 컴포넌트들을 조합하는 방식은 "무엇을 보여줄 것인가"에 집중하는 선언형 UI 개발을 가능하게 하는 핵심 기반입니다. 개발자는 각 컴포넌트가 어떤 UI를 담당하는지 선언하고, 이들을 조합하여 최종적인 애플리케이션 UI를 구성합니다. 이는 자바스크립트에서 DOM을 직접 조작하며 UI의 '방법'을 하나하나 명령해야 했던 방식에서 벗어나, UI의 '결과'에만 집중할 수 있게 하여 개발자의 인지 부하를 줄이고 생산성을 높이는 리액트의 중요한 특징입니다.

자바스크립트 vs 리액트 차이

구분자바스크립트 (ES6 모듈)리액트 (컴포넌트 모듈)
모듈화 목적코드 재사용성 증대, 전역 스코프 오염 방지컴포넌트 기반 UI 구축, 선언적 UI 개발, 강력한 재사용성
모듈 정의 단위유틸리티 함수, 상수 등 기능 단위 코드UI 조각 및 관련 로직을 포함하는 컴포넌트
의존성 관리import 시 명시적 파일 경로 및 확장자 .js 필수 명시, 개발자가 직접 관리빌드 도구 (번들러)를 통한 자동화된 의존성 관리 및 경로 처리
파일 구조 원칙기능별 파일 분리에 중점컴포넌트별 파일 분리 및 UI 트리 구조에 따른 계층적 구성
개발 환경<script type="module">을 통한 브라우저 직접 지원 또는 번들러 사용Next.js, Vite 등 프레임워크/빌드 도구 환경 필수, 모듈 번들링 자동 적용

요약

이번 편에서는 자바스크립트 모듈 시스템과 리액트 컴포넌트 기반 모듈화의 차이점을 탐구했습니다. 단순히 코드를 분리하고 재사용하는 것을 넘어, 각 패러다임에서 모듈화가 어떤 의미를 가지고 어떻게 활용되는지 깊이 있게 살펴보는 기회가 된 것 같습니다.

  • 자바스크립트 모듈화: 코드 재사용성, 전역 스코프 오염 방지, 명시적 파일 경로/확장자, 개발자 의존성 관리, 번들링 필요성
  • 리액트 컴포넌트 모듈화: 컴포넌트 기반 UI 구축, 빌드 도구 자동화, 간결한 import, UI 로직 집중, 선언형 UI/재사용성
  • 모듈화의 진화: 기능 단위 코드 분리에서 컴포넌트 기반 UI 재사용 및 효율성 극대화로 진화

다음 파트(Part 3)에서는 상호작용을 다루며, 3-1편에서 이벤트 처리(Responding to Events)로 본격적인 인터랙션을 시작합니다.

참고문서

– [컴포넌트 import 및 export하기 (Importing and Exporting Components)]