리액트/기초

리액트 기초 01 - 핵심 요소 1 : JSX 요소 Fragment

lamarcK 2025. 4. 6. 17:19

Fragment (프래그먼트)란 무엇인가?

리액트는 html을 JS 내에서 구현할 수 있도록 JSX(JavaScript XML) 문법을 따른다. 때문에 컴포넌트가 반환하는 여러 요소들을 무조건 하나의 부모 요소로 감싸야한다. 그런데 부모 요소로 <div> 태그를 쓰면 불필요한 실제 DOM 요소가 추가 돼버린다. 아무런 기능을 가지지 않은 <div> 로 감싸여 있어도 실제로는 css가 제대로 구현되지 않는 문제가 발생할 수도 있다.

예를들면

  • 불필요한 div로 인해 CSS 레이아웃 깨짐
  • Flexbox나 Grid 레이아웃 적용 시 추가 div로 인한 스타일링 오류
  • 의미 없는 DOM 요소 증가

같은 문제 말이다.

 

그런 문제점을 해결하기 위해 리액트에서 도입한 기능 중 하나가 바로 Fragment(프래그먼트)라는 빈 래퍼(wrapper)를 추가해서 요소를 그룹화하는 것이다. Fragment는 실제로 DOM에 추가되지 않는 태그이다.

* 래퍼(wrapper) 는 특정 요소를 감싸는 요소를 의미한다. Fragment는 실제 DOM에 추가되지 않는 내용이 없는 래퍼이기 때문에 빈 (empty) 래퍼라고 부른다.

 

아래 코드에서 프래그먼트는 html요소의 가장 위쪽 <>과 가장 끝쪽 </> 부분이다.

정식 명칭은 React.Fragment 혹은 Fragment (<>)라고 부르는데 <>부분은 React.Fragment의 축약형이다. 기본적으로는 축약형으로 쓰인다. 모양만 다르지 개념적으로 실질적으로 동일한 것이다.

// 축약형
function App() {
  return (
    <>
      <div>내용</div>
    </>
  )
}

// 명시적 방식 (키 속성 사용 등)
function List() {
  return (
    <React.Fragment key={item.id}>
      <div>항목</div>
    </React.Fragment>
  )
}

기존에 <div> 태그로 감싸야 하는 부분을 <>로 대체해서 일어날 수 있는 특정 오류들을 방지할 수 있다.

때문에 굳이 부모 태그로 <div>를 보내야 하는 경우가 아니라면 프래그먼트를 사용하는 것이 바람직하다.

 

React.Fragment 와 Fragment (<>)가 완벽히 동일하진 않다.

완벽히 동일했으면 굳이 문법적으로 React.Fragment를 남겨 둘필요가 없었을 것이다.

React.Fragment는 실제 DOM에는 반영되지 않기 때문에 거의 사용되진 않지만 굳이 써야 하는 경우 아래와 같은 방법으로 사용될 때가 있다.

1. key 속성이 필요할 때

{items.map(item => (
  <React.Fragment key={item.id}>
    <div>{item.name}</div>
    <div>{item.description}</div>
  </React.Fragment>
))}
  • 리스트의 각 항목을 고유하게 식별
  • React의 재조정(reconciliation) 알고리즘에서 성능 최적화
  • 변경된 부분만 정확히 업데이트

2. 추가 속성을 부여해야 할 때

테스트나 특수한 상황에서만 제한적으로 사용한다.

<React.Fragment className="custom-class">
  <div>내용1</div>
  <div>내용2</div>
</React.Fragment>

 

최상위 컨테이너 가 없을 경우 오류 발생!

JSX 문법에선 반드시 최상위 요소에 모든 요소를 감싸는 컨테이너가 존재해야한다. 때문에 최상위 컨테이너가 없이 형제 요소만 있는 경우 오류가 난다. (아래의 경우 프래그먼트를 사용해서 에러가 나는 것이 아니다! div 였어도 형제 요소라 오류가 난다.)

1. 최상위 컨테이너 X 형제 요소만 있어서 오류가 나는 경우

// 이건 불가능 (에러)
return (
  <>
    <h1>그룹1</h1>
  </>
  <>
    <h2>그룹2</h2>
  </>
)

 

2. 최상위 컨테이너가 있어서 괜찮은 경우

아래처럼 실제로 최상위 컨테이너가 있으면 에러가 나지 않는다. 프래그먼트를 어떤 용도로 사용하든 괜찮고 최상위 컨테이너가 굳이 프래그먼트가 아니어도 아무 상관없다.

// 이건 가능
return (
  <>
    <>
      <h1>그룹1</h1>
    </>
    <>
      <h2>그룹2</h2>
    </>
  </>
)

 

정리

  1. 최상위에 하나의 <>가 있어야 함
  2. 그 안에서는 자유롭게 <> 사용 가능
  3. 단, JSX 문법만 지키면 됨
  4. 실제 HTML에는 Fragment가 표시되지 않음

Fragment (프래그먼트) 도입 이유

그렇다면 Fragment는 굳이 왜 도입한 걸까? 

앞서도 이유는 몇개 언급했지만 전체적으로는 아래 이유들 때문이다.

 

1. JavaScript 함수의 단일 반환값 제약 해결
   - 함수는 하나의 값만 반환 가능(즉 리턴을 한번에 1개의 '값' 밖에 못한다.)
   - JSX도 하나로 묶어서 반환해야 함


2. 불필요한 DOM 요소 추가 방지
   - div 같은 실제 태그 대신 사용
   - 실제 HTML 구조를 깔끔하게 유지


3. CSS 스타일링 문제 해결
   - 불필요한 div로 인한 스타일 충돌 방지
   - 의도하지 않은 스타일 상속 문제 해결

4. 성능 최적화
   - 실제 DOM에 추가되지 않아 메모리 효율적
   - 렌더링 성능에 영향 없음

 

보충 + : 함수는 하나의 값만 반환 가능

이부분은 자세히 말하자면 Return을 할 때 값을 1개 밖에 반환하지 못한다는 것이다. 리턴문이 값을 1개 밖에 반환하지 못하는 것은 코드의 예측가능성 등을 위해서 자바스크립트 자체에서 의도적으로 걸어둔 제약이다.

// 이건 불가능
function test() {
    return value1, value2;  // 마지막 값만 반환됨
}

// 여러 값을 반환하려면 무조건 하나의 객체나 배열로 감싸야 함
function test() {
    return {value1, value2};  // 객체로 감싸기
    // 또는
    return [value1, value2];  // 배열로 감싸기
}

또한 착각하면 안되는 것이  return value1, value2; 에서 마지막 값만 반환되는 것은 콤마의 연산자의 특성이다.

모든 표현식이 순서대로 실행되고 값을 반환할 때는 마지막 것만 반환한다.

즉 return 실행 전에 value1, value2를 실행하고 value2을 반환한 상태(연산 결과 값)에서 리턴이 value2를 반환하는 것이다.

 

때문에 콤마 연산자가 아닌 각기 1줄로 코드가 작성되어 있을 경우엔 value1을 반환하고 함수가 종료된다.

return 자체가 값을 반환하는 즉시 함수를 종료하는 기능이 있기 때문이다.

때문에 break를 함수 종료로 사용하는 이유도 이런 기능때문이다. 함수 '전체'가 종료되기 때문에 내부 함수만 벗어나거나 해당 루프, 블록만 종료하려면 break를 사용해야한다.

function test() {
    return value1;  // 리턴 되는 즉시 함수 종료
    return value2;  // 절대 실행되지 않음
}

 

물론 값을 굳이 반환할 필요가 없을 경우 리턴으로 종료할수도 있다

function test() {
    if(조건) {
        return;  // 값 없이 함수 종료
    }
    // 코드 계속
}