
개발을 하다 보면 어느새 import 구문이 줄줄이 늘어져서 코드 위쪽만 봐도 스트레스가 쌓일 때가 있습니다.
특히 컴포넌트가 많아지고 폴더가 깊어질수록 길어지는데,
이럴 때 유용하게 쓸 수 있는게 바로 barrel export 구조입니다.
Barrel Export란?
Barrel export는 여러 모듈(파일)을 하나의 진입점(index.ts 또는 index.js 등)에서 모아서 export 해주는 방식입니다.
이 방식을 사용하면 외부에서 그 폴더 내 모듈을 사용할 때 훨씬 간단하게 import 할 수 있습니다.
[예시]
아래 두가지 컴포넌트가 있다고 가정해보겠습니다.
// components/Button.tsx
export const Button = () => { /* ... */ };
// components/Input.tsx
export const Input = () => { /* ... */ };
그럼 보통 아래처럼 import 하죠?
import { Button } from '../components/Button';
import { Input } from '../components/Input';
그런데 components/index.ts에 이렇게 모아두면:
// components/index.ts
export * from './Button';
export * from './Input';
이제는 아래처럼 깔끔하게 쓸 수 있습니다.
import { Button, Input } from '../components';
언제 쓰면 좋을까?
- 공통 컴포넌트들이 많은 경우 (버튼, 폼, 모달 등)
- 유틸 함수가 여러 개 흩어져 있는 경우
- Redux slice, hooks, context처럼 모듈이 많아질 때
- 디자인 시스템 구성할 때
결국 내가 자주 import하게 될 폴더는 배럴을 쓰는게 코드 가독성과 유지보수에 좋습니다.
[실제 디렉토리 구조 예시]
components/
├── Button.tsx
├── Input.tsx
├── Modal.tsx
└── index.ts // <- 배럴 파일
index.ts:
export * from './Button';
export * from './Input';
export * from './Modal';
그래서 장점을 정리해보자면
- import 경로가 짧아지고 깔끔해짐
- 디렉토리 구조에 대한 추상화 -> 폴더 안 구조가 바뀌어도 외부에는 영향 X
- 협업 시 가독성과 관리 편의성이 올라감
하지만 주의할 점이 있습니다.
첫 번째로, 모든 곳에 무조건 쓰는건 오히려 독이 될 수 있습니다.
Barrel export가 편리하긴 하지만, 모든 폴더마다 무조건 index.ts 파일을 만들고 export를 다 모으는 건 비효율적일 수 있습니다.
예를 들어,
- 어떤 폴더는 단 하나의 파일만 가지고 있는 경우
- 해당 모듈이 외부에서 자주 사용되지 않는 경우
이런 상황에서 굳이 배럴 파일을 만들면, 오히려 파일 수만 많아져서 관리가 복잡해지고, 혼란스러울 수 있습니다.
일반적으로
- 공통적으로 자주 import되는 모듈들 (ex. 버튼, 입력창, 유틸 함수들)
- 디자인 시스템 컴포넌트
- 여러 파일이 관련된 기능 집합
등에만 선택적으로 적용하는 것이 좋습니다.
두 번째로, 사이드 이펙트(side effect) 있는 모듈은 배럴에 넣지 않는 것이 좋습니다.
Barrel export는 주로 "재노출(re-export)"만을 담당해야 합니다. 그런데 만약 어떤 모듈이 import될 때 실행되는 사이드 이펙트가 있다면, 그 모듈을 배럴에 넣는 건 위험합니다.
예를 들어,
// store/init.ts
console.log('전역 상태 초기화!');
initializeGlobalStore();
이런 코드를 그냥 export * from './init' 식으로 배럴에 포함시키면,
해당 폴더에서 어떤 모듈을 import하든 간접적으로 init.ts가 실행되버리고 예기치 않은 동작이나 버그로 이어질 수 있습니다.
즉
- 상태 초기화
- DOM 조작
- 전역 이벤트 등록 등
실행 자체가 어떤 부작용을 일으키는 모듈은 가급적 직접 import 하고, 배럴에는 포함하지 않는 게 안전합니다.
세 번째로, 순환 참조(Circular Dependency)에 주의해야합니다.
배럴 구조를 쓰다 보면, 여러 파일이 서로 연결되고, export를 다시 묶다 보면 무심코 순환 참조가 생기기도 합니다.
예를 들어
- A.ts가 B.ts를 import
- B.ts가 C.ts를 import
- C.ts가 A.ts를 import
이런 상황은 에러를 일으키거나, 모듈이 undefined 상태로 들어오는 문제를 발생시킬 수 있습니다.
특히 배럴 구조에서 이런 일이 쉽게 생깁니다.
해결 방법으로는,
- index.ts에서는 최대한 "단방향"으로만 export 하기
- 내부적으로 사용하는 파일과 외부로 노출할 파일을 구분해서 설계하기
- 가능한 순환 참조 탐지를 위한 ESLint 설정 활용하기
* 참고로 TypeScript는 순환 참조가 있더라도 큰 에러 없이 돌아가는 경우도 있어서, 문제를 미리 알아채기 어려운 경우가 있습니다. 그래서 더 주의가 필요합니다.
처음에는 귀찮아 보여도, 프로젝트가 커지고 유지보수가 시작되면 Barrel export의 진가를 느낄 수 있습니다.
'Dev' 카테고리의 다른 글
직접 써본 FSD (Feature-Sliced Design) 적용기: 깔끔함과 혼란 사이 (0) | 2025.04.05 |
---|---|
[OAuth] OAuth 2.0을 파헤쳐보자 🔍 + 소셜로그인 예제 (0) | 2025.03.05 |
[OAuth] GCP (Google Cloud Platform)에서 Client ID 생성하기 🪪 (0) | 2025.03.05 |
[OAuth] OAuth란? 🤔 (0) | 2025.03.04 |
React+MapBox Geojson 올리기 (Height 속성 활용하기 ) (0) | 2025.03.04 |