본문 바로가기

리액트 심화 스터디

[Week 4] Compound Component Pattern, HOC, React Portal

1. Compound Component Pattern

Compound Component Pattern 여러 개의 하위 컴포넌트들이 하나의 상위 컴포넌트 내에서 작동하도록 설계하는 방식이다.

이 패턴은 하나의 컴포넌트를 여러 하위 컴포넌트들의 조합으로 분리하여 재사용성을 높인다.

 

Compound Component Pattern 예시

1) Header.jsx (최상위 컴포넌트)

import React from 'react';

const Header = ({ children, ...props }) => {
  return <header className="header" {...props}>{children}</header>;
};

Header.Logo = ({ children }) => <div className="logo">{children}</div>;
Header.Navigation = ({ children }) => <nav className="navigation">{children}</nav>;
Header.Search = ({ children }) => <div className="search">{children}</div>;

export default Header;
  • Header:
    Header는 최상위 컴포넌트로, children을 받아서 그 안에 포함된 Logo, Navigation, Search 컴포넌트를 렌더링한다.
  • Header.Logo, Header.Navigation, Header.Search:
    Header 컴포넌트의 하위 컴포넌트로, Header의 특정 부분을 구성하는 독립적인 요소이다.

2) App.jsx (Header 사용)

import React from 'react';
import Header from './Header';

function App() {
  return (
    <Header>
      <Header.Logo>맥도날드</Header.Logo>
      <Header.Navigation>
        <ul>
          <li>버거</li>
          <li>맥런치</li>
          <li>맥모닝</li>
        </ul>
      </Header.Navigation>
      <Header.Search>검색</Header.Search>
    </Header>
  );
}

export default App;
  • App.jsx에서는 Header 컴포넌트 하위에 어떤 컴포넌트들이 있는지 쉽게 파악할 수 있다.
  • 또한, 필요한 하위 컴포넌트만 골라서 사용할 수도 있다. 예를 들어, 로고와 검색 기능의 표시 여부를 제어해야 하는 경우, 원하는 하위 컴포넌트만 골라서 사용하면 되므로 무분별한 props를 방지할 수 있고 코드의 복잡성을 줄일 수 있다.
  • 비록 위 예시는 간단하지만, 공통 컴포넌트를 구현할 때 이 패턴을 활용하면 더 효율적으로 관리할 수 있을 것 같다.

 

2. HOC(고차 컴포넌트, Higher-Order Component)

Higher-Order Component 기존의 컴포넌트를 인자로 받아 새로운 컴포넌트를 반환하는 함수이다.

 

HOC 예시

import React from "react";
import { Redirect } from "react-router-dom";

const withAuth = (WrappedComponent) => {
  return (props) => {
  	const isAuthenticated = Boolean(localStorage.getItem("token"));

    if (!isAuthenticated) {
      return <Redirect to="/login" />;
    }

    return <WrappedComponent {...props} />;
  };
};

const MyPage = () => <div>My Page</div>;

export default withAuth(MyPage);
  • 이처럼 HOC는 동일한 로직을 여러 컴포넌트에 재사용해야 할 때 유용하다.
  • 또한, 인증 로직이 변경되더라도 HOC 내부만 수정하면 되므로 유지보수하기 좋다.

 

3. React Portal

React Portal 부모 DOM 계층 바깥에 컴포넌트를 렌더링하기 위해 사용된다.

 

ReactDOM.createPortal(child, container)
  • 첫 번째 인자는 부모 DOM 계층 바깥에 렌더링하고 싶은 컴포넌트를 가리키고,
  • 두 번째 인자는 이 컴포넌트를 띄울 DOM 엘리먼트를 가리킨다.

 

React Portal 예시

1) index.html

<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>1 to 50</title>
  </head>
  <body>
    <div id="root"></div>
    <div id="modal"></div>
  </body>
</html>
  • 모달이 렌더링 될 위치에 div 엘리먼트를 추가해준다.

2) ModalPortal.jsx

import ReactDom from "react-dom";

const ModalPortal = ({ children }) => {
  const el = document.getElementById("modal");
  return ReactDom.createPortal(children, el);
};

export default ModalPortal;
  • 원하는 컴포넌트를 위에서 추가한 DOM 엘리먼트에 띄우기 위한 포탈을 생성해준다.

3) Game.jsx

import styled from "@emotion/styled";
import ModalPortal from "./ModalPortal";
import AlertModal from "./AlertModal";

const Game = ({ level, startGame, endGame, finalTime, resetState }) => {

  return (
    <Container>
      {/* 코드 중략 */}
      
      {/* 게임 종료 모달 */}
      <ModalPortal>
        {isModalOpen && (
          <AlertModal
            time={finalTime}
            onClose={() => {
              setIsModalOpen(false);
              initGame();
            }}
          />
        )}
      </ModalPortal>
    </Container>
  );
};

export default Game;
  • 띄우고 싶은 모달을 위 코드처럼 ModalPortal로 감싸주면 된다.