게으른개발너D

React에서 쓰이는 Design Pattern 본문

개발/ReactJS

React에서 쓰이는 Design Pattern

lazyhysong 2024. 9. 18. 21:50

React에서 자주 사용되는 디자인 패턴들은 상태 관리, 컴포넌트 구조, 코드 재사용성 등과 관련되어 있다.

디자인 패턴들은 상황에 맞게 선택하여 사용할 수 있으며, 복잡한 애플리케이션에서는 상태 관리나 컴포넌트 구조를 명확히 하는 데 큰 도움이 된다.

대표적인 몇 가지 패턴을 간단한 예시와 함께 소개하겠다.

 

 

 

 

Container-Presenter Pattern

 


이 패턴은 컴포넌트의 역할을 명확히 분리하기 위해 자주 사용된다.

Container 컴포넌트는 상태와 비즈니스 로직을 관리를, Presenter 컴포넌트는 UI를 담당한다.

// Presenter: UI를 담당
const UserProfile = ({ user }) => (
  <div>
    <h1>{user.name}</h1>
    <p>{user.email}</p>
  </div>
);

// Container: 데이터 처리와 상태 관리
const UserProfileContainer = () => {
  const [user, setUser] = useState(null);

  useEffect(() => {
    fetchUser().then(setUser); // API 호출을 통해 사용자 정보 가져오기
  }, []);

  if (!user) return <div>Loading...</div>;

  return <UserProfile user={user} />;
};

 

 

 

Compound Components Pattern

컴파운드 컴포넌트 패턴은 여러 컴포넌트가 서로 상호작용하는 복잡한 UI를 만들 때 유용하다.

부모 컴포넌트가 자식 컴포넌트 간의 관계를 관리하며, 자식 컴포넌트가 독립적으로 동작할 수 있게 한다.

const Dropdown = ({ children }) => {
  const [isOpen, setIsOpen] = useState(false);
  
  return (
    <div>
      <button onClick={() => setIsOpen(!isOpen)}>Toggle</button>
      {isOpen && <div>{children}</div>}
    </div>
  );
};

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

const App = () => (
  <Dropdown>
    <DropdownItem>Item 1</DropdownItem>
    <DropdownItem>Item 2</DropdownItem>
    <DropdownItem>Item 3</DropdownItem>
  </Dropdown>
);

 

 

 

Render Props Pattern

 

컴포넌트에서 데이터를 렌더링할 때 렌더링 로직을 외부에서 정의할 수 있도록 하는 패턴이다.

고차 컴포넌트 (HOC Compoenents Pattern)보다 유연성이 높다.

const MouseTracker = ({ render }) => {
  const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });

  const handleMouseMove = (e) => {
    setMousePosition({ x: e.clientX, y: e.clientY });
  };

  return <div onMouseMove={handleMouseMove}>{render(mousePosition)}</div>;
};

const App = () => (
  <MouseTracker render={({ x, y }) => (
    <h1>The mouse position is ({x}, {y})</h1>
  )} />
);

 

 

 

Higher-Order Components (HOC) Pattern

 

HOC는 기존 컴포넌트를 확장하여 기능을 추가하는 패턴이다.

중복 코드를 줄이고, 재사용 가능한 로직을 구현할 수 있다.

const withLoading = (Component) => {
  return function WithLoadingComponent({ isLoading, ...props }) {
    if (isLoading) return <div>Loading...</div>;
    return <Component {...props} />;
  };
};

const UserList = ({ users }) => (
  <ul>
    {users.map(user => <li key={user.id}>{user.name}</li>)}
  </ul>
);

const UserListWithLoading = withLoading(UserList);

const App = () => {
  const [isLoading, setIsLoading] = useState(true);
  const [users, setUsers] = useState([]);

  useEffect(() => {
    fetchUsers().then(data => {
      setUsers(data);
      setIsLoading(false);
    });
  }, []);

  return <UserListWithLoading isLoading={isLoading} users={users} />;
};

 

 

 

Custom Hooks Pattern

 

React Hook을 재사용하기 쉽도록 만든 패턴으로, 컴포넌트 외부로 상태 관리 및 로직을 추출해 모듈화한다.

const useFetchData = (url) => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch(url)
      .then(response => response.json())
      .then(data => {
        setData(data);
        setLoading(false);
      });
  }, [url]);

  return { data, loading };
};

const App = () => {
  const { data, loading } = useFetchData('https://api.example.com/data');

  if (loading) return <div>Loading...</div>;

  return <div>Data: {JSON.stringify(data)}</div>;
};

 

 

 


유행이 지난 패턴?

 

위의 대표적인 디자인 패턴은 모두 유용해 보이나, 현재 필요성이 없어져 유행이 지나 다른 패턴으로 대체되거나 잘 사용하지 않는 패턴들이 있다. 최근 트렌드와 개발 환경에 따라 많이 쓰이는 패턴과 사용 빈도가 줄어든 패턴을 구분해보겠다.

 

유행이 지난 패턴

 

  1. Higher-Order Components (HOC):
    • HOC는 한때 널리 사용되었지만, React 16.8에서 Hooks가 도입된 이후로 그 사용이 점차 줄어들고 있다. HOC는 복잡한 코드 구조와 디버깅 문제를 야기할 수 있어서 Custom Hooks로 대체되는 경우가 많다.
    • 대체 기술: Custom Hooks 또는 Render Props.
  2. Render Props:
    • Render Props도 HOC처럼 로직을 재사용하기 위한 좋은 방법이었지만, Hooks 도입 이후 많은 경우 Custom Hooks로 대체되었다. Render Props는 복잡한 컴포넌트 구조를 만들 수 있기 때문에 가독성이 떨어질 수 있다.
    • 대체 기술: Custom Hooks.

 

 

많이 쓰이는 패턴

 

  1. Custom Hooks Pattern:
    • React Hooks는 매우 많이 사용되며, 특히 상태 관리, 비즈니스 로직, API 호출 등을 분리해 관리할 때 자주 사용된다. 
    • React의 함수형 컴포넌트 구조와 완벽하게 조화를 이루며, 상태와 효과를 쉽게 관리할 수 있도록 도와주기때문에 재사용이 가능하고 가독성 높은 코드를 작성할 수 있다..
  2. Component Composition (컴포넌트 합성):
    • 이 패턴은 컴포넌트를 여러 개의 작은 컴포넌트로 나누어 재사용성을 높이는 방식이다. React에서 권장하는 방법이며, 중첩된 구조보다는 컴포넌트를 조합하는 방식으로 UI를 구축한다.
    • 이를 통해 복잡한 UI를 관리하기 쉬워지고, 상태와 UI를 더 잘 분리할 수 있다.
  3. Context API와 Reducer 사용:
    • Context APIuseReducer를 조합하여 전역 상태를 관리하는 패턴이다. Redux 같은 외부 라이브러리 없이도 복잡한 상태 관리를 가능하게 한다.
    • React 자체에서 제공하는 기능으로 간단한 상태 관리가 가능하고, 복잡한 의존성을 줄일 수 있다.
  4. Atomic Design (원자적 디자인):
    • 컴포넌트 기반 설계 방식으로, 컴포넌트를 원자(Atomic), 분자(Molecular), 유기체(Organisms) 등의 단위로 구분해 설계하는 방법이다. UI를 모듈화하고 일관성 있는 디자인 시스템을 만들기 위해 많이 사용된다.
    • UI 컴포넌트 라이브러리를 구축할 때나 대규모 프로젝트에서 재사용 가능한 컴포넌트를 구성하기에 적합하다.
  5. React Query, Zustand와 같은 상태 관리 라이브러리 사용:
    • React Query와 같은 라이브러리는 서버 상태 관리와 캐싱을 쉽게 해주고, Zustand는 간결한 전역 상태 관리 솔루션으로 인기를 끌고 있다.
    • Redux와 같은 복잡한 상태 관리 라이브러리보다 더 간단하고 사용하기 쉬운 옵션들이 주목받고 있다.

 

아래 예시는 React Query를 사용하여 서버 데이터를 관리하고, Custom Hook을 통해 API 호출 로직을 분리한 예시이다. 

import { useQuery } from 'react-query';

// Custom Hook
const useFetchUserData = (userId) => {
  return useQuery(['user', userId], () =>
    fetch(`https://api.example.com/users/${userId}`).then(res => res.json())
  );
};

const UserProfile = ({ userId }) => {
  const { data, isLoading, error } = useFetchUserData(userId);

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      <h1>{data.name}</h1>
      <p>{data.email}</p>
    </div>
  );
};

const App = () => <UserProfile userId={1} />;

 

 

결론!

요즘은 Custom Hooks, Component Composition, Context API와 같은 패턴이 많이 사용되며, React QueryZustand와 같은 경량 상태 관리 라이브러리의 사용도 트렌드에 맞는 방식이다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Comments