개발 일기

25.03 2주 WIL

와라리요 2025. 3. 16. 23:14

나는 si 업체에서 react, ts로 프론트를 개발하고 있다. 기존 html, css, js, jquery로 코딩을 하다. 올해부터 프론트는 react, ts로 코딩하기로 했다. 


 

react 디자인 패턴

 - 독학으로 react, ts를 공부하고 업무로 사용하는 것을 처음이라 고민을 많이 했다. 우선 디자인 패턴을 정하고 그것에 맞추어 작업한 적이 없어 이번은 디자인 패턴에 맞추어 작업할 것이며 지금 진행 중에 있다.

 백엔드 api도 규격화가 되어 있어 다른 프로젝트들에서도 재사용이 가능한 컴포넌트들은 Container & Presentational 또는 Composition으로 하고 있으며 page들은 Custom Hook으로 진행하고 있다.


 

컨테이너 & 프레젠테이션 패턴 (Container & Presentational Pattern)

UI 로직과 비즈니스 로직을 분리하는 패턴

  • Presentational Component (프레젠테이션 컴포넌트): UI를 담당하고, 상태를 직접 관리하지 않음. props를 통해 데이터를 받음.
  • Container Component (컨테이너 컴포넌트): 상태를 관리하고 데이터를 가져오는 역할을 함. 프레젠테이션 컴포넌트를 감싸고 데이터 및 이벤트 핸들러를 전달함.

 

예제

// Presentational Component (UI 담당)
const UserProfile = ({ name, age }: { name: string; age: number }) => (
  <div>
    <h1>{name}</h1>
    <p>나이: {age}</p>
  </div>
);

// Container Component (데이터 관리)
const UserProfileContainer = () => {
  const [user, setUser] = useState({ name: "홍길동", age: 30 });

  return <UserProfile name={user.name} age={user.age} />;
};

 

사용 이유

  • UI와 로직을 분리해 재사용성을 높임
  • 테스트하기 쉬움 (UI 컴포넌트는 props를 기반으로 렌더링되므로)

 

컴포지션 패턴 (Composition Pattern)

React에서 가장 추천하는 패턴으로, 컴포넌트를 조합하여 기능을 확장

  • props.children을 활용하여 UI를 유연하게 구성

 

예제

const Card = ({ children }: { children: React.ReactNode }) => (
  <div style={{ border: "1px solid gray", padding: "16px", borderRadius: "8px" }}>
    {children}
  </div>
);

// 사용 예시
const App = () => (
  <Card>
    <h2>제목</h2>
    <p>내용을 여기에 작성하세요.</p>
  </Card>
);

 

사용 이유

  • 가독성이 뛰어나고 유지보수성이 높음
  • 재사용성이 좋으며, 부모 컴포넌트가 자식의 UI를 결정할 수 있음

 

커스텀 훅 패턴 (Custom Hook Pattern)

비즈니스 로직을 재사용하기 위해 만든 훅 함수

  • 여러 컴포넌트에서 공통적으로 사용하는 기능(예: API 호출, 로컬 상태 관리)을 분리
  • useSomething() 형태로 작성

 

예제

const useFetchUser = (userId: string) => {
  const [user, setUser] = useState(null);

  useEffect(() => {
    fetch(`/api/user/${userId}`)
      .then((res) => res.json())
      .then((data) => setUser(data));
  }, [userId]);

  return user;
};

// 컴포넌트에서 사용
const UserProfile = ({ userId }: { userId: string }) => {
  const user = useFetchUser(userId);
  
  if (!user) return <p>Loading...</p>;
  return <p>이름: {user.name}</p>;
};

 

 

사용 이유

  • 코드 중복을 줄이고 로직을 모듈화
  • 여러 곳에서 재사용 가능
  • 유지보수성이 높아짐

 

ts 인터페이스 및 View 데이터 처리

API 호출 후 응답 데이터에서 프로퍼티가 누락되거나, 키는 존재하지만 값이 null인 경우가 발생할 수 있다.
이를 해결하기 위해 인터페이스를 ?(optional) 또는 | null 타입으로 선언하면, 뷰(컴포넌트)에서 조건문이 많아져 코드가 복잡해지는 문제가 생긴다.

이를 방지하기 위해, API 응답을 가공하는 함수를 통해 undefined 또는 null 값을 인터페이스에 정의된 타입에 맞는 기본값으로 변환하는 방식을 적용했다.
이러한 방식은 Adapter 패턴과 유사하며, API 응답 데이터를 컴포넌트가 다루기 쉬운 형식으로 변환하여 유지보수성과 가독성을 높인다.


 

API 응답 데이터 변환 예제 (Adapter 패턴 적용)

// 사용자 데이터 인터페이스 정의
interface User {
  id: number;
  name: string;
  age: number;
  email: string;
}

// 기본값 설정 함수 (Adapter 역할)
const normalizeUser = (data?: Partial<User>): User => ({
  id: data?.id ?? 0,
  name: data?.name ?? "Unknown",
  age: data?.age ?? 0,
  email: data?.email ?? "no-email@example.com",
});

// API 응답 데이터 처리
const fetchUser = async (userId: number): Promise<User> => {
  const response = await fetch(`/api/user/${userId}`);
  const data = await response.json();
  return normalizeUser(data); // 데이터 변환 후 반환
};

// 컴포넌트에서 사용
const UserProfile = ({ userId }: { userId: number }) => {
  const [user, setUser] = useState<User | null>(null);

  useEffect(() => {
    fetchUser(userId).then(setUser);
  }, [userId]);

  if (!user) return <p>Loading...</p>;
  return (
    <div>
      <h1>{user.name}</h1>
      <p>나이: {user.age}</p>
      <p>이메일: {user.email}</p>
    </div>
  );
};

 

이 방식의 장점

  • API 응답에서 일부 값이 undefined 또는 null이더라도 컴포넌트에서 조건문 없이 안전하게 사용 가능
  • **데이터 변환 로직을 분리(Adapter 패턴 활용)**하여, API 응답 구조가 변경되더라도 컴포넌트 코드 수정 최소화
  • 타입 안정성(Type Safety) 강화, API 호출 시 예상치 못한 undefined로 인해 발생하는 런타임 오류 방지

 

React와 MVVM 패턴 비교

이러한 구조는 MVVM 패턴과 유사한 형태를 가지며, React에서는 다음과 같이 대응될 수 있다.

MVVM 패턴 React 구조 설명
Model API 데이터 & 상태 관리
(React Query, Redux, Zustand 등)
백엔드 API에서 데이터를 가져오고 상태를 관리
ViewModel Custom Hook (useFetchUser 등) &
Container Component
비즈니스 로직을 캡슐화하고,
View에서 필요한 데이터를 가공
View Presentational Component UI를 렌더링하는 역할,
로직 없이 props를 기반으로 동작

 

MVVM 패턴과 유사한 구조의 장점

  • 데이터 로직과 UI 로직을 분리하여 유지보수 용이
  • Custom Hook을 활용해 재사용 가능한 데이터 처리 로직 구현 가능
  • API 응답을 표준화(정규화)하여 View에서 불필요한 검증 로직 제거