zero-wiki Help

12장-리액트-디자인-패턴

리액트에서 널리 사용되는 몇 가지 디자인 패턴을 살펴봅니다.

  • 고차 컴포넌트(Higher-Order Component, HOC) 패턴

  • 렌더 프로퍼티(Render Props) 패턴

  • Hooks 패턴

  • 정적 가져오기

  • 동적 가져오기

  • 코드 스플리팅

  • PRPL 패턴

  • 로딩 우선순위

  • 리스트 가상화

12.2 고차 컴포넌트

컴포넌트에 특정 스타일을 적용하거나, 인증을 요구하거나, 전역 상태를 추가하는 등 동일한 로직을 여러 컴포넌트에서 재사용하고 싶을 때 사용할 수 있는 패턴입니다.

이때 고차 컴포넌트(HOC) 패턴은 로직을 재사용하는 효과적인 방법 중 하나입니다.

function withLogging(Component) { return (props) => { const onClick = () => { props.onClick(); logging(); // 로그를 출력하는 함수 }; return <Component onClick={onClick} {...props} />; }; } const CustomButton = () => <button>Click Me!</button>; const LoggingButton = withLogging(CustomButton);

12.2.2 장점

  • 재사용 로직을 한 곳에 모아 관리할 수 있습니다.

  • 코드의 DRY 원칙을 지킬 수 있으며, 관심사를 효과적으로 분리할 수 있습니다.

12.2.3 단점

  • 대상 컴포넌트에 전달하는 prop의 이름이 충돌할 수 있습니다.

  • 따라서 예제처럼 별도의 onClick 핸들러를 구현해주는 것이 좋습니다.

  • 여러 HOC를 조합하여 사용하는 경우 어떤 prop을 제공하는지 파악하기 어려울 수 있습니다.

12.3 렌더 프로퍼티 패턴

렌더 프로퍼티(Render Prop)는 JSX 요소를 반환하는 함수를 prop으로 전달하는 패턴입니다.

<CustomButton innerComponents={() => <span>render prop!</span>} />

렌더 프로퍼티를 통해 컴포넌트 간 로직을 공유할 수 있습니다.

const CustomButton = ({ innerComponents }) => { return ( <button> {innerComponents ? innerComponents() : 'click me!'} </button> ); };

12.3.3 장점

  • 여러 컴포넌트 간 로직과 데이터를 쉽게 공유할 수 있습니다.

  • render 또는 children prop을 활용하여 컴포넌트의 재사용성을 높일 수 있습니다.

  • HOC에서 발생할 수 있는 이름 충돌 문제를 방지할 수 있습니다.

12.3.4 단점

  • 라이프사이클 관련 메서드를 prop에 추가할 수 없습니다.

  • 데이터 변경보다는 렌더링에 집중된 컴포넌트에 적합합니다.

12.4 리액트 Hooks 패턴

리액트 16.8부터 도입된 Hooks를 사용하면 클래스 컴포넌트 없이도 상태와 라이프사이클 기능을 사용할 수 있습니다.

12.4.1 클래스 컴포넌트

Hooks 도입 이전에는 상태와 라이프사이클 기능을 사용하기 위해 클래스 컴포넌트를 사용해야만 했습니다.

12.4.3 복잡성 증가

클래스 컴포넌트에 많은 로직이 추가되면 코드가 커지고 중복이 발생하며, 성능 최적화도 어려워졌습니다.

12.4.4 훅의 장점

  • 코드 라인 수를 줄일 수 있습니다.

  • 복잡한 컴포넌트를 단순화할 수 있습니다.

  • 상태 관련 로직을 재사용할 수 있습니다.

  • UI에서 분리된 로직을 공유할 수 있습니다.

12.4.5 Hook vs Class

  • Hook은 복잡한 계층 구조를 피하고, 코드를 더 명확하게 만듭니다.

  • HOC나 Render Prop을 사용하는 클래스보다 디버깅이 쉽습니다.

  • Hook은 리액트 컴포넌트에 일관성을 부여하며, 클래스보다 이해하기 쉽습니다.

12.5 정적 가져오기

import 키워드를 사용하면 다른 모듈에서 내보낸 코드를 정적으로 로드할 수 있습니다.

import CustomButton from './CustomButton'; const CustomForm = () => { // ... };

정적 import는 초기 번들에 포함되기 때문에, 모든 컴포넌트를 정적으로 가져올 경우 초기 로딩 속도에 영향을 줄 수 있습니다.

12.6 동적 가져오기

유저와의 상호작용 이후에 필요한 모듈을 동적으로 로드할 수 있습니다.

import React, { useState, Suspense, lazy } from 'react'; const CustomModal = lazy(() => import('./custom-modal')); const LoginForm = () => { const [modalOpen, setModalOpen] = useState(false); const handleModal = () => { setModalOpen((prev) => !prev); }; return ( <Suspense fallback={<div>Loading...</div>}> <button onClick={handleModal}> {modalOpen ? '모달 닫기' : '모달 열기'} </button> {modalOpen && <CustomModal />} </Suspense> ); };

이런 방식으로 초기 번들 크기를 줄여 사용자 경험을 향상시킬 수 있습니다.

12.7 코드 스플리팅

애플리케이션이 복잡해질수록 코드를 여러 개의 번들로 나누는 것이 중요합니다.

12.7.1 경로 기반 분할

react-router와 같은 라이브러리를 사용하면 현재 경로에 따라 컴포넌트를 동적으로 로드할 수 있습니다.

12.7.2 번들 분할

웹팩이나 롤업과 같은 번들러는 소스 코드를 여러 번들로 나누고, 이를 필요할 때 로드할 수 있도록 지원합니다.

브라우저는 V8 엔진을 통해 데이터를 파싱, 컴파일, 실행합니다. 번들이 크면 이 모든 과정이 느려집니다.

따라서 초기 렌더링에 꼭 필요한 코드만 먼저 로드하고, 나머지는 이후에 로드하도록 설정하는 것이 좋습니다.

12.8 PRPL 패턴

PRPL 패턴은 성능 최적화를 위한 전략으로, 전 세계 사용자들이 쾌적하게 애플리케이션을 사용할 수 있도록 돕습니다.

  • Push: 주요 리소스를 서버에서 빠르게 푸시

  • Render: 초기 경로를 빠르게 렌더링

  • Pre-cache: 자주 방문하는 경로의 에셋을 미리 캐싱

  • Lazy-load: 자주 사용되지 않는 리소스는 지연 로딩

브라우저는 서버에서 HTML과 기타 리소스를 받아오지만, 반복적인 요청은 비효율적입니다.

리소스를 받으면 브라우저는 이를 캐시에 저장하며, 이후에는 빠르게 사용할 수 있습니다.

단, 서버 푸시는 캐시를 인식하지 못하기 때문에 푸시된 리소스는 다음 방문 시 재요청될 수 있습니다. 이를 해결하기 위해 서비스 워커를 사용해 리소스를 캐시하는 방식이 PRPL의 핵심입니다.

12.9 로딩 우선순위

중요한 리소스를 먼저 불러와야 하는 경우 preload를 통해 우선순위를 지정할 수 있습니다.

<link rel="preload" href="/main.js" as="script" />
  • FCP, LCP, TTI 등의 성능 지표를 개선할 수 있습니다.

  • 단, 잘못 사용하면 렌더링 지연 또는 화면 밀림(LCP 지연)이 발생할 수 있습니다.

12.9.1 SPA의 Preload

SPA에서 초기 렌더링 시 즉시 표시되어야 하는 컴포넌트는 메인 번들에 포함되지 않고 병렬로 로드되어야 합니다. 이를 위해 webpack의 special comment를 사용할 수 있습니다.

const FirstComponent = import(/* webpackPreload: true */ "./first-component");

12.10 리스트 가상화

리스트 가상화는 대규모 리스트를 렌더링할 때 성능을 향상시키는 기술입니다.

화면에 보이는 항목만 렌더링하고 나머지는 렌더링하지 않아 불필요한 DOM 연산을 줄일 수 있습니다.

12.10.4 웹 플랫폼의 발전

최신 브라우저는 content-visibility 속성을 지원합니다. auto로 설정하면 화면 밖 콘텐츠는 렌더링을 지연시킬 수 있습니다.

.list-item { content-visibility: auto; }

이를 통해 렌더링과 페인팅 성능을 더욱 향상시킬 수 있습니다.

Last modified: 04 April 2025