[이론] React 디자인 패턴

조민수·어제
0

개발 이론

목록 보기
12/12

React 디자인 패턴

이전 게시글에서 다뤘듯이, 디자인 패턴이란 소프트웨어를 개발하는 과정에서의 설계 패턴을 정의한 것이다.
프론트엔드에서도 시대가 흐르면서, 복잡한 화면 작업, React, Vue 등의 프레임워크의 도입으로
새로운 패턴 형성 및 기존 패턴의 구체적 구조화가 필요해졌다.

특히, 이러한 과정에서 어떻게 컴포넌트를 구성/활용할 것인가에 대한 고민이 깊어지고 이것이 발전해 React 디자인 패턴이 되었다.

React 디자인 패턴은

  • 코드베이스의 일관성
  • 모듈성 및 확장성

에 초점을 둔다.

이를 통해

  • 개발 시간 및 비용 감소
  • 기술 부채의 축적 완화
  • 유지보수성 증가
  • 전체적인 개발 프로세스의 간소화
  • 소프트웨어의 품질 향상

을 이뤄낸다.


1. Presentation & Container

  • 가장 기본적인, 근본 패턴
  • 데이터 로직을 수행하는 Container와 데이터를 출력하는 Presentation을 분리해 구현

장점

  • 역할별 분리를 통해 명확한 기능과 책임을 구분
  • 컴포넌트간 낮은 의존도, Presentation 컴포넌트의 재사용 가능
  • 기능과 UI의 분리를 통해 높은 가독성과 유지보수성 확보
  • UI 컴포넌트를 추출해 편리한 마크업 및 UI 재사용

단점

  • hooks 도입 이후, 로직 분리가 이전 대비 쉬워지며 추천하지 않는 패턴이 됨

즉, UI만을 구성하는 jsx와 로직을 핸들링하는 jsx를 따로 구성한다.


2. Custom Hook

  • 컴포넌트의 로직을 재사용 가능한 함수로 캡슐화
  • 복잡한 로직을 추상화해 높은 가독성과 유지보수성 확보
  • 로직 모듈화를 통해 단위 테스트를 쉽게

장점

  • 공통 로직을 별도의 함수로 캡슐화 → 코드 재사용성 촉진
  • 캡슐화된 로직에 대해 구체적이고 집중된 단위 테스트 수행 → 테스트 용이성 향상

단점

  • 로직이 단일 컴포넌트에만 특화될 땐 사용하지 않는게 낫다.
  • 즉, 다수의 커스텀 훅 생성은 추가적인 복잡성을 초래

사용 예시

import {useState, useEffect} from 'react';
// Custom Hook 정의
const useFetch = (url) => {
	const [data, setData] = useState(null);
  
  	useEffect(() => {
    	fetch(url)
      		.then((res) => res.json())
      		.then((data) => {
				setData(data);
			});
	}, [url]);
  	
  	return {data}
}

// 컴포넌트에서 사용

export default function App() {
	const {data} = useFetch(`...`);
    ...
}

3. HOC : 고차 컴포넌트

  • 컴포넌트를 인자로 받아 새로운 컴포넌트를 반환
  • 공통된 로직을 재사용, 새로운 기능을 추가하기 위해
  • 여러 컴포넌트 간 공유하는 로직이 있을 때

장점

  • 컴포넌트 간 로직을 캡슐화, 공유 → 코드 재사용 촉진
  • Presentation 로직과 Business 로직을 명확히 구분
  • 함수형 디자인 패턴을 적용해 코드 구성 및 모듈화 촉진

단점

  • 과도한 HOC는 디버깅이 어려운 복잡한 컴포넌트를 야기
  • 컴포넌트 계층 구조를 숨겨 App.의 구조에 대한 이해가 어려워질 수 있음

사용 예시

import React from 'react';
// HOC 함수 정의
const withLogger = (WrappedComponent) => {
	return function EnhancedComponent(props) {
    	return <WrappedComponent {...props} />;
    }
}

// 일반 컴포넌트
const NormalComponent = ({name}) => {
	return <h2>HI {name}!</h2>;
}

// HOC 사용 컴포넌트
const HOCLogger = withLogger(NormalComponent);

// 활용
export default function App() {
	return <HOCLogger name = "Minsu" />;
}

4. Atomic Design

  • 디자인 시스템에서 컴포넌트 단위를 효율적으로 구성하는 방식
  • UI 컴포넌트를 설계하고, 재사용 가능한 컴포넌트를 만들기 위해 사용

구조

  1. Atoms : UI의 가장 작은 단위
    (Button, Input, Lable ... )
  2. Molecules : 여러 Atoms의 집합, 간단한 컴포넌트
    (LoginInput, LoginToggle 등)
  3. Organisms : 여러 Molecules + Atoms로 복잡한 UI 구성
    (LoginComponent 등)
  4. Templates : Organisms를 배치해 페이지 구조 정의
    (Header + Title + LoginComponent = LoginTemplate)
  5. Pages : Templates에 실제 데이터를 적용한 완성된 페이지

장점

  • 높은 UI 재사용성
  • 스타일 가이드 도구 사용 용이 및 쉬운 테스트 단계
  • 디자인에서의 일관성

단점

  • 디자인 시스템을 구축하기 위한 초기 비용 필요
  • 로직, 상태 공유에서 Props Drilling발생 가능
  • @media, MediaQuery등 반응형 UI 구축에 어려움

5. Compound Components

  • 서로 밀접하게 협력하여 작동하는 컴포넌트 생성
  • 부모 컴포넌트가 여러 자식 컴포넌트를 캡슐화,
    그들 간의 통신 및 조정된 상호작용 가능하게 함
  • 주로 상태(state)와 컨텍스트(Context) 공유
  • 복잡한 UI를 유연하게 조립

장점

  • 관련 로직을 컴포넌트 집합에 캡슐화, 재사용
  • 복합 컴포넌트와 상호작용하는 명확하고 일관된 API 제공
  • 여러 컴포넌트를 하나로 결합해 더 큰 유연성과 사용자 정의를 가능하게 함

단점

  • 컴포넌트 간 상호작용 이해에 불필요한 복잡성 필요
  • 설계에서의 어려움

사용 예시

import React, { createContext, useContext, useState } from 'react';

const TabsContext = createContext();

const Tabs = ({children}) => {
	const [activeIdx, setActiveIdx] = useState(0);
  return(
  	<TabsContext.Provider value={{activeIdx, setActiveIdx}}>
    	{children}
	</TabsContext.Provider>
  );
}

const TabList = ({ children }) => {
  return <div>{children}</div>;
}

const Tab = ({ idx, children }) => {
  const { activeIdx, setActiveIdx } = useContext(TabsContext);
  return (
    <button
      onClick={() => setActiveIdx(idx)}
      style={{ fontWeight: activeIdx === idx ? 'bold' : 'normal' }}
    >
      {children}
    </button>
  );
}

export default function App() {
  return (
    <Tabs>
      <TabList>
        <Tab index={0}>Tab 1</Tab>
        <Tab index={1}>Tab 2</Tab>
      </TabList>
    </Tabs>
  );
}

6. Render Props

  • 함수를 props로 전달해, 컴포넌트 내부에서 원하는 대로 렌더링할 수 있게 해줌
  • 주로 복잡한 렌더링 로직을 외부에서 주입할 때 사용
  • 컴포넌트의 특정 부분을 렌더링하는 부분을 Props로 제공된 함수에 위임하여 재사용성, 유연성 제공
  • 여러 컴포넌트 간 렌더링 로직 공유 시에 활용

장점

  • 다른 패턴보다 유연하게 렌더링 로직을 재사용 가능
  • PresentationBusiness 로직을 분리 구성

단점

  • 데이터 흐름에서의 복잡성
  • ReactProps에 대한 깊은 이해 필요

사용 예시

  1. Counter.jsx : Render Props 구현
import React, { useState } from 'react';

export const Counter = ({render}) => {
  const [count, setCount] = useState(0);

  const increment = () => setCount(count + 1);
  const decrement = () => setCount(count - 1);

  return (
    <div>
      {render({ count, increment, decrement })}
    </div>
  );
}
  1. App.jsx : 사용
...
import Counter from './Counter';

export default const App = () => {
  return (
    <div>
      <h1>Render Props Example</h1>
      
      {/* 첫 번째 예시: 기본 카운터 */}
      <Counter
        render={({ count, increment, decrement }) => (
          <div>
            <h2>Basic Counter</h2>
            <p>Count: {count}</p>
            <button onClick={increment}>Increment</button>
            <button onClick={decrement}>Decrement</button>
          </div>
        )}
      />

      <hr />

      {/* 두 번째 예시: 스타일이 다른 카운터 */}
      <Counter
        render={({ count, increment, decrement }) => (
          <div>
            <h2>Styled Counter</h2>
            <p style={{ fontWeight: 'bold' }}>Count: {count}</p>
            <button onClick={increment}>➕ Add</button>
            <button onClick={decrement}>➖ Subtract</button>
          </div>
        )}
      />
    </div>
  );
}

7. VAC : (View, Action, Container)

  • 컴포넌트를 세가지 역할로 분리해 UI와 상태관리, 비즈니스 로직을 구조화
  • 비즈니스 로직 뿐 아니라 UI로직에서도 렌더링 관심사를 명확하게 분리
  • 반복, 조건부 렌더링, 스타일 제어 등 렌더링 관련 처리 만 수행
  • props를 통해서만 제어, 스스로의 상태를 관리하거나 변경하지 않음(stateless)

구조

  1. View : 순수한 Presentation 컴포넌트, UI 렌더링
  2. Action : 사용자 이벤트 처리를 위한 컴포넌트 (btnClick 등)
  3. Container : 데이터 fetching, 상태관리, 비즈니스 로직 처리,
    View - Action 연결
  • MVC 패턴과 유사

장점

  • UI 개발자와 로직 개발자가 다를 경우, 업무 구분 명확 및 충돌 최소화

단점

  • Props로만 데이터 흐름을 제어하기 때문에 Props Drilling을 야기할 수 있고, 코드가 복잡해질 수 있음

마치며...

단순히 이러한 디자인 패턴을 인지하는 것을 넘어,
직면한 프로젝트에 어떤 패턴이 알맞을 지를 고민하는 것이 중요하다.

예를 들어, UI의 높은 재사용성과 일관된 디자인을 위해 Atomic Design을 도입하자! 라고 할 때, props관리의 복잡성을 당연히 먼저 고려해야 한다는 것이다.

따라서 필요시엔 여러 디자인 패턴을 효율적으로 함께 사용하고,
환경에 맞는 규칙을 새롭게 만들어 적용해야한다.

또한 당연하게도 어떤 디자인 패턴을 적용하느냐에 따라
파일 구조 및 테스팅 적용이 달라지기 때문에 패키지 구조 설정 단계 ~ 테스트 단계까지 높은 관심과 고민이 필요하다.

[참고자료]

profile
사람을 좋아하는 Front-End 개발자
post-custom-banner

0개의 댓글