프론트엔드 실력 향상을 위한 나의 판단 기준 점검

김현준·2025년 7월 12일
0

잡다한

목록 보기
11/11

프론트엔드를 공부하면서 개념은 대부분 익숙해졌지만, 최근 실무형 프로젝트들을 진행하며 판단의 기준이 애매한 지점들을 자주 마주하게 되었다.
이 글은 그중에서도 가장 고민이 컸던 영역들인 컴포넌트 추상화, 상태 위치, 렌더링 최적화에 대한 나의 흔들림을 돌아보고, 프로젝트를 통해 개선 방향을 정리한 글이다.

약한 부분 – 정리되지 않은 부분이 존재

코어 개념은 알고 있지만, 연결 지점에서 약간의 흔들림이 있음

  • 예: 컴포넌트 추상화 vs 재사용성 트레이드오프, 상태를 어디에 둘 것인가 같은 판단 기준

핵심 개선사항으로 추정되는 부분

  • 상태 관리 기준 정립
    • 상태를 local / global / 서버 상태로 나누고, 그 기준을 명확히 세우는 글이나 정리 작성

1. 컴포넌트 추상화 vs 재사용성 트레이드오프

예시

  • Button, Modal, Card 등의 UI 컴포넌트를 설계할 때

    • 너무 추상화하면 → props가 많아져 복잡성 증가
    • 너무 구체화하면 → 상황마다 새로 만들게 되어 재사용성 하락
잘못된 판단 예문제점
BaseButton 하나로 다 통일variant, loading, icon 등 props 폭발
상황마다 버튼 만들기 (SubmitButton, LoginButton)중복 코드 증가, 유지보수 어려움

나의 문제
언제 추상화하고 언제 그냥 둘 것인지 기준이 없었다.

  • 개선 방안
    • 예를들어 카드 컴포넌트가 있다면 타이틀, 이미지, 설명 등으로 쪼갬
    • 샤드씨엔처럼 가져다 쓰면 조금씩 다른 모양의 카드에 대응 가능

2. 상태를 어디에 둘 것인가? (State Placement)

흔들림 포인트

  • 검색 페이지의 필터 상태(type, tag, order)를 Zustand에 넣었지만,
    사실 SSR & 공유 관점에선 URL 쿼리로 관리하는 게 더 적절했음
상태 위치특징
local (useState)간단하지만 페이지 이동 시 초기화됨
global (Zustand 등)재사용 가능하지만 과도한 전역화 위험
URL (searchParams)공유, 리프레시에 유리하나 조작 복잡
server state (TanStack Query)API 동기화에 유리하나 local과 혼용 시 혼란

나의 문제
이 상태가 진짜 전역이어야 하는가?"를 항상 의심해야 한다.


3. 임시 UI 상태를 서버 캐시나 전역 상태로 관리

흔들림 포인트

  • 북마크만의 낙관적 UI 처리를 위해 콘텐츠 전체를 SSR 캐시와 CSR 캐시를 공유하는 전략을 사용
  • 결과적으로 단순한 UI 토글만을 위해 전체 콘텐츠 데이터를 setQueryData()로 직접 조작
  • 사실상 아이콘 상태와 숫자 증가만 필요한 UI 상태였는데, 너무 무거운 단위의 데이터를 건드림
  • 결과
    • 코드 복잡도 증가
    • 서버/클라이언트 캐시 충돌 가능성
    • 캐시 추적 포인트 증가 → 디버깅 어려움

문제 화면

  • 녹색 표시한 부분: 북마크 상태를 낙관적으로 반영하려고 전체 콘텐츠 데이터를 수정했음
  • 사실상 useState() 한 줄로도 충분했음

기존 구조

 queryClient.setQueryData(logKeys.detail(logId), (old: ApiResponse<DetailLog>) => {
  if (!old?.success) return old;

  const updatedPlaces = old.data.place.map((place) => {
    if (place.place_id === placeId) {
      const currentCount = place._count?.place_bookmark ?? 0;
      return {
        ...place,
        _count: {
          ...place._count,
          place_bookmark: currentCount + (isBookmark ? -1 : 1),
        },
      };
    }
    return place;
  });

  return {
    ...old,
    data: {
      ...old.data,
      place: updatedPlaces,
    },
  };
});
  • 구조가 깊고, place._count.place_bookmark처럼 중첩 객체 수정이 반복
  • 타입 안정성 유지가 어렵고, 코드도 비직관적

개선 구조

export default function PlaceBookmarkWithCount({
  placeId,
  placeBookmarkCount = 0,
}: PlaceBookmarkWithCountProps) {
  const [bookmarkCount, setBookmarkCount] = useState(placeBookmarkCount);

  const handleToggle = (newStatus: boolean) => {
    setBookmarkCount((prev) => prev + (newStatus ? 1 : -1));
  };

  return (
    <section className="absolute top-0 right-0 flex flex-col items-center">
      <PlaceBookMarkButton // 장소 낙관적 업데이트 코드가 들어있는 컴포넌트
        placeId={placeId}
        onToggle={handleToggle}
        className="!top-0 !right-0 !relative w-9 h-9"
      />
      <span className="font-medium text-text-sm text-light-300">
        {formatCount(bookmarkCount)} //북마크 카운트
      </span>
    </section>
  );
}
  • 콘텐츠 데이터는 서버 캐시 데이터
  • bookmarkCount는 단순 UI 표시이므로 useState로 관리
  • 북마크 시 해당 콘텐츠만 서버 캐시 무효화
  • 로직 단순화

후기

개발 실력은 개념 암기보다 상황에서의 선택 기준이 핵심이다.
이번 사례를 통해 흔들렸던 판단 지점을 돌아봤고, 앞으로 더 명확한 기준을 갖고 설계할 수 있도록 꾸준히 개선해 나갈 예정이다.

profile
기록하자

0개의 댓글