[React] React.Fragment에 대하여

박기영·2023년 1월 4일
1

React

목록 보기
19/32

클론 코딩을 하며 공부를 하던 중,
map()을 사용하여 영화 목록을 보여주는 과정에서 React.Fragment를 사용하는 것을 보았다.
필자였다면 최상위 엘리먼트로 <div>를 쓰고 넘어갔을 것 같은데...
React.Fragment는 왜 사용하는걸까? 이유와 예시를 알아보도록 하자.

React.Fragment

A common pattern in React is for a component to return multiple elements. Fragments let you group a list of children without adding extra nodes to the DOM.
- React 공식 docs -

컴포넌트가 여러 개의 엘리먼트를 반환하는 것은 리액트에서 흔한 패턴이다. Fragments는 당신이 DOM에 추가적인 노드를 추가하지 않고도 자식 요소들을 그룹화 할 수 있게 해준다.
- React 공식 docs -

공식 문서에서는 React.Fragment에 대하여 위와 같은 멘트와 함께 설명을 시작한다.
여러 자식 요소들을 그룹화하기 쉽다는 말이 애매하게 보인다.
예시를 살펴보자.

일반적인 그룹화

아래와 같은 컴포넌트를 반복 생성해야하는 상황이다.

// ItrateComponent.jsx

function ItrateComponent(props) {
  return (
    <div>
      <h2>{props.num}번 반복 컴포넌트입니다.</h2>
      <p>반복은 너무 귀찮다.</p>
    </div>
  );
}

export default ItrateComponent;
// App.js

import ItrateComponent from "./IterateComponent";

export default function App() {
  const arr = [1, 2, 3, 4, 5];

  return (
    <div>
      {arr.map((num, index) => (
        <ItrateComponent num={num} />
      ))}
    </div>
  );
}

생성된 화면은 다음과 같을 것이다.

참고 이미지

개발자 도구를 통해 HTML 태그 상태를 살펴보자.

참고 이미지

App.js를 기준으로 보면

<div>
  // 여기부터 ItrateComponent.jsx
  <div>
    <h2></h2>
    <p></p>
  </div>
  <div>
    <h2></h2>
    <p></p>
  </div>
  
  // ... //
  
</div>

여기서 주목할 것은 다름아닌, ItrateComponent.jsx에서의 div 태그이다.
div 태그는 아무 의미가 없는 태그이기 때문에, 구획을 나눌 때 대충 쓰기 좋은 태그이다.

단, 의미가 없기 때문에 불필요하다.
문제는...React에서는 최상위 태그 아래에 자식 요소를 넣어줘야지만 컴포넌트를 사용할 수 있다.

즉,

// ItrateComponent.jsx

function ItrateComponent(props) {
  return (
    // 아래 두 태그를 감싸던 최상위 div 태그가 사라졌다.
      <h2>{props.num}번 반복 컴포넌트입니다.</h2>
      <p>반복은 너무 귀찮다.</p>
  );
}

export default ItrateComponent;

이렇게 사용하면 에러가 발생한다는 것이다.
우리는 반드시 최상위 태그를 작성해야한다.

자, 여기서 공식 문서의 말을 한번 떠올려보자.

Fragments는 당신이 DOM에 추가적인 노드를 추가하지 않고도 자식 요소들을 그룹화 할 수 있게 해준다.

오호...이제 이 말을 이해할 수 있겠다.
위 예시에서 사용한 불필요한 div 태그를 없앨 수 있다는 뜻이구나!

Fragment 예시

앞서 우리는 Fragment가 불필요한 태그 생성을 없앨 수 있게 해준다는 것을 알았다.

ItrateComponent.jsx의 코드를 아래와 같이 수정해보겠다.

import React from "react";

function ItrateComponent(props) {
  return (
    <React.Fragment>
      <h2>{props.num}번 반복 컴포넌트입니다.</h2>
      <p>반복은 너무 귀찮다.</p>
    </React.Fragment>
  );
}

export default ItrateComponent;
import { Fragment } from "react";

function ItrateComponent(props) {
  return (
    <Fragment>
      <h2>{props.num}번 반복 컴포넌트입니다.</h2>
      <p>반복은 너무 귀찮다.</p>
    </Fragment>
  );
}

export default ItrateComponent;
function ItrateComponent(props) {
  return (
    // 단축된 React.Fragment 사용법
    <>
      <h2>{props.num}번 반복 컴포넌트입니다.</h2>
      <p>반복은 너무 귀찮다.</p>
    </>
  );
}

export default ItrateComponent;

위 세 가지 예시는 모두 같은 코드이다.
문법만 조금 달라질 뿐이다.

이렇게 사용해도 페이지 렌더링 결과는 같다.
개발자 도구를 사용해서 HTML 태그쪽을 살펴보자.

참고 이미지

div 태그를 사용했던 때와 비교했을 때 어떤 것이 달라졌을까?
...이미 질문 속에 정답이 있다.
div 태그가 사라졌다!!
그런데 렌더링된 화면에는 전혀 변화가 없다. 그냥 아까랑 똑같다.

즉, 최상위 태그를 만들기 위해 div를 사용하지 않고도, 불필요하게 노드를 추가하지 않고도,
React.Fragment를 사용하면 최상위 노드(태그)를 만들어주지 않아도 컴포넌트를 사용할 수 있다!

공식 문서에는 이를 더 명확하게 보여주는 예시가 있다.

공식 문서 예시

class Columns extends React.Component {
  render() {
    return (
      <div>
        <td>Hello</td>
        <td>World</td>
      </div>
    );
  }
}
class Table extends React.Component {
  render() {
    return (
      <table>
        <tr>
          <Columns />
        </tr>
      </table>
    );
  }
}

table 태그 내에서 Columns 컴포넌트를 사용하는데 문제는..
컴포넌트를 사용하기 위해서는 최상위 노드로 무조건 감싸줘야한다는 것이다.

그래서 Columns 컴포넌트에 불필요하게 div를 사용해야만 한다.
그래서 HTML 엘리먼트의 구조는 아래와 같아진다.

<table>
  <tr>
    <div>
      <td>Hello</td>
      <td>World</td>
    </div>
  </tr>
</table>

이 문제를 해결해주는 것이 Fragment이다.

Columns 컴포넌트를 아래와 같이 변경하자.

class Columns extends React.Component {
  render() {
    return (
      <React.Fragment>
        <td>Hello</td>
        <td>World</td>
      </React.Fragment>
    );
  }
}

최상위 노드로 div 대신 React.Fragment를 사용한 것을 볼 수 있다.
이렇게 되면 HTML 엘리먼트의 구조는 아래와 같아진다.

<table>
  <tr>
    <td>Hello</td>
    <td>World</td>
  </tr>
</table>

불필요하게 노드를 추가하는 일을 지양할 수 있게 해준다는 것을 확인 가능하다.

key가 있는 Fragments

공식 문서에서는 Fragment 사용 시, key가 필요한 경우에는
반드시 React.Fragment 문법으로 명시적인 표시를 해줘야한다고 한다.

function Glossary(props) {
  return (
    <dl>
      {props.items.map(item => (
        // React는 `key`가 없으면 key warning을 발생합니다.
        <React.Fragment key={item.id}>
          <dt>{item.term}</dt>
          <dd>{item.description}</dd>
        </React.Fragment>
      ))}
    </dl>
  );
}

재밌게도 key 속성만이 React.Fragment에 사용할 수 있는 유일한 속성이다.
이벤트 핸들러 등은 나중에 추가될 예정인 것 같다.(v18.2.0 공식 문서 기준)

이 부분도 앞서 살펴봤던 예시에 적용해보자.
사실..map()을 사용한다면 key 속성을 반드시 넣어야한다.
그렇지 않으면 콘솔창에 에러를 보여줄 것이다.

즉, App.js

import ItrateComponent from "./IterateComponent";

export default function App() {
  const arr = [1, 2, 3, 4, 5];

  return (
    <div>
      {arr.map((num, index) => (
        <ItrateComponent num={num} key={index} />
      ))}
    </div>
  );
}

이런 식으로 key 속성을 같이 적어주는 것이 옳은 방법이다.
음...이러면 React.Fragment를 사용하는 느낌이 나지 않으니
ItrateComponentApp.js에 직접 써보겠다.

import React from "react";

export default function App() {
  const arr = [1, 2, 3, 4, 5];

  return (
    <div>
      {arr.map((num, index) => (
        <React.Fragment key={index}>
          <h2>{num}번 반복 컴포넌트입니다.</h2>
          <p>반복은 너무 귀찮다.</p>
        </React.Fragment>
      ))}
    </div>
  );
}
import { Fragment } from "react";

export default function App() {
  const arr = [1, 2, 3, 4, 5];

  return (
    <div>
      {arr.map((num, index) => (
        <Fragment key={index}>
          <h2>{num}번 반복 컴포넌트입니다.</h2>
          <p>반복은 너무 귀찮다.</p>
        </Fragment>
      ))}
    </div>
  );
}
// 에러 발생!

export default function App() {
  const arr = [1, 2, 3, 4, 5];

  return (
    <div>
      {arr.map((num, index) => (
        <key={index}>
          <h2>{num}번 반복 컴포넌트입니다.</h2>
          <p>반복은 너무 귀찮다.</p>
        </>
      ))}
    </div>
  );
}

이런 식으로 활용 할 수 있겠다.
다만, 단축 문법에서는 key 속성을 사용할 수 없으니 이 점은 주의해야한다!

결론

예시로 인한 혼동이 있을 수 있는데
map() 같은 반복 렌더링이 있을 때 써야된다는 것이 아니다.
최상위 노드(엘리먼트)를 불필요하게 만들어야할 상황에서 쓰는 것이다!
이 점을 혼동하지 말자.

참고 자료

React 공식 문서
chief님 블로그
7942yongdae님 블로그

profile
나를 믿는 사람들을, 실망시키지 않도록

0개의 댓글