TestForge | Aidevops | 📊 Plogger ✍️ Blog 📚 Docs
plogger

AI DevOps Korea

AI 서비스 개발, 운영, 성능개선을 하나의 루프로 연결합니다

aidevops.kr에서 LLMOps, RAG, AI Agent, 관측성, 평가, 비용-성능 최적화를 실전 운영 관점으로 정리합니다.

Vue 3 Composition API 완벽 가이드

· 수정 4월 16일
Vue 3 Composition API 완벽 가이드 다이어그램
이 글에서 다루는 핵심 흐름, 아키텍처 구조, 주요 판단 포인트를 한눈에 이해할 수 있도록 정리한 그림입니다.
Vue 3의 Composition API는 단순히 문법이 바뀐 것이 아니라 컴포넌트 로직을 더 작은 단위로 분해하고 재조합할 수 있게 만든 설계 도구입니다. 실무에서 중요한 것은 `ref`, `reactive`, `computed`의 이름을 외우는 것보다 “어떤 로직을 어디까지 composable로 분리할 것인가”를 이해하는 것입니다.

왜 Composition API가 필요한가

Options API는 작은 컴포넌트에서는 읽기 쉽지만, 컴포넌트가 커질수록 관련된 로직이 data, methods, watch, computed에 흩어집니다. Composition API는 이 로직들을 기능 단위로 묶을 수 있게 해줍니다.

즉, 핵심 가치는 “재사용”보다 먼저 “응집도”입니다.

setup은 진입점이지 모든 것을 넣는 곳이 아니다

import { ref, computed } from 'vue';

export default {
  setup() {
    const count = ref(0);
    const double = computed(() => count.value * 2);

    function increment() {
      count.value += 1;
    }

    return { count, double, increment };
  },
};

초기에는 setup() 안에 모든 코드를 몰아넣기 쉽지만, 실무에서는 금방 거대 함수가 됩니다. 이 시점부터 composable로 분리해야 합니다.

ref와 reactive는 역할이 다르다

  • ref: 단일 값, 명확한 상태 단위
  • reactive: 여러 필드가 함께 움직이는 객체

실무에서는 ref를 기본값처럼 쓰고, 여러 필드가 의미적으로 하나의 객체일 때만 reactive를 쓰는 편이 예측 가능성이 높습니다. reactive는 편하지만 구조 분해나 추적 경계가 흐려질 수 있기 때문입니다.

composable은 재사용 함수가 아니라 기능 모듈이다

import { ref, onMounted } from 'vue';

export function useUserProfile(userId) {
  const profile = ref(null);
  const loading = ref(false);
  const error = ref(null);

  async function fetchProfile() {
    loading.value = true;
    error.value = null;
    try {
      const res = await fetch(`/api/users/${userId}`);
      profile.value = await res.json();
    } catch (e) {
      error.value = e;
    } finally {
      loading.value = false;
    }
  }

  onMounted(fetchProfile);

  return { profile, loading, error, fetchProfile };
}

좋은 composable은 보통 하나의 기능을 캡슐화합니다. 예를 들어 인증, 사용자 프로필, 페이지네이션, 스크롤 상태처럼 말입니다. 이렇게 하면 UI 컴포넌트는 표현에 집중하고 로직은 composable로 분리됩니다.

watch는 강력하지만 남용하기 쉽다

Composition API에서 watch는 외부 변화에 반응할 때 유용하지만, 상태 흐름을 불투명하게 만들 수도 있습니다. 가능하면 computed로 표현 가능한 것은 먼저 computed로 두고, 부수효과가 필요할 때만 watch를 쓰는 편이 좋습니다.

실무에서 자주 생기는 문제

  • setup()이 너무 커져서 오히려 읽기 어려워지는 경우
  • composable이 네트워크, 상태, 라우팅, UI까지 모두 떠안는 구조
  • reactive 객체를 무분별하게 넘겨 추적 경계를 잃는 상황
  • 템플릿에서 너무 많은 로직을 직접 처리하는 패턴

Composition API는 자유도가 높아서 구조화 원칙이 없으면 금방 무질서해집니다.

추천하는 기준

  • 화면 로직은 composable로 묶기
  • UI 표현 상태와 비즈니스 상태를 구분하기
  • composable 이름을 useXxx 패턴으로 일관되게 유지하기
  • 외부 API 호출은 composable 안에서 캡슐화하되 지나치게 범용화하지 않기
  • 작게 시작하고, 반복되는 로직이 생길 때 분리하기

정리

Composition API의 핵심은 더 많은 기능을 쓰는 것이 아니라 로직을 더 응집도 있게 배치하는 데 있습니다. setup()은 출발점일 뿐이고, 실무 품질은 composable 경계를 얼마나 잘 설계하느냐에서 갈립니다. 그 기준만 잘 잡으면 Vue 컴포넌트는 훨씬 읽기 쉽고 재사용 가능하며 변경에 강한 구조가 됩니다.

운영 환경에서 어려워지는 지점

  • Composition API는 composable의 책임이 분명할 때 잘 확장된다. 범용 쓰레기통처럼 쓰면 오히려 더 나빠진다.
  • 횡단 reactive 상태를 module-level state로 암묵 공유하면 추적이 어려워진다.
  • 반응성은 강력해서 앱이 커질 때까지 결합을 숨기고 변경 동작을 놀랍게 만들 수 있다.

중요한 아키텍처 결정

  • composable은 페이지네이션, 인증 세션 접근, 폼 오케스트레이션처럼 안정적인 책임 기준으로 만든다.
  • 호출자마다 독립 상태를 반환하는지, 공유 singleton 상태를 반환하는지 명확히 한다.
  • 부작용은 가능한 한 소비 코드 가까이에 보이도록 둔다.

실무 예시

신뢰할 수 있는 composable은 하나의 응집된 기능과 의도된 상태 형태를 노출한다.

export function usePagination(initialPage = 1) {
  const page = ref(initialPage)
  const pageSize = ref(20)

  function next() {
    page.value += 1
  }

  return { page, pageSize, next }
}

피해야 할 안티패턴

  • 파일을 짧게 만들려고 페이지 전용 로직을 composable로 옮기는 것.
  • invariant가 불분명한 거대한 reactive 묶음을 반환하는 것.
  • 수명주기와 reset 동작 설명 없이 module-level ref를 공유하는 것.

운영 체크리스트

  • composable 이름과 소유권을 주기적으로 검토한다.
  • 상태 격리가 중요하면 여러 소비자 인스턴스 시나리오를 테스트한다.
  • 공유 상태 composable은 명시적으로 문서화한다.
  • 업데이트 트리거 디버깅이 어려운 반응성 체인을 감시한다.

최종 판단

Composition API는 composable이 읽기 쉬운 행위 단위를 만들 때 강하다. “재사용 가능”이 단지 “다른 곳으로 옮김”을 뜻하면 약해진다.

Continue Reading

다음으로 읽기 좋은 글

다음 탐색

이 주제를 시스템 관점으로 더 이어서 보기