[Vue] Composable 구조화

배창민·2025년 11월 28일
post-thumbnail

Composable 구조화 정리

Composition API를 제대로 쓰려면 ComposablePinia를 어떻게 나눌지부터 감이 잡혀야 한다.


1. Composable이란 무엇인가

Composable = 재사용 가능한 Composition API 함수

  • Vue 3에서 setup() 안에 마구 섞여 있던 로직을 역할별로 분리해 별도 함수로 뽑아낸 것

  • 예를 들어 강의 목록 화면에 있는:

    • 강의 목록 불러오기
    • 검색/필터링
    • 로딩/에러 상태 관리
      이런 것들을 전부 useCourses() 같은 함수로 빼서, 다른 컴포넌트에서도 그대로 재사용할 수 있게 만드는 패턴

Composable을 쓰면 생기는 장점

  1. 재사용성

    • 여러 컴포넌트에서 같은 로직을 반복해서 쓰지 않고, 한 번만 구현해서 가져다 쓴다.
  2. 역할 분리

    • 화면 컴포넌트는 “UI와 이벤트 바인딩”에만 집중
    • 데이터 로딩, 필터링 같은 로직은 Composable이 담당
  3. 테스트 용이

    • UI 없이 Composable 함수만 따로 테스트하기 쉽다.
  4. 유지보수 편의

    • 로직을 수정할 일이 생겨도 View 파일을 뒤지지 않고 Composable만 고치면 된다.

한 줄로 정리하면:

Composable은 Vue에서 비즈니스 로직을 UI에서 떼어낸, 독립적인 재사용 모듈이라고 보면 된다.


2. 리팩토링 전 구조의 한계

리팩토링 전 강의 목록 화면은 대충 이런 느낌이다.

  • onMounted 안에서 직접 fetchCourses() 호출
  • 검색/필터링을 컴포넌트 안에서 computed로 정의
  • 즐겨찾기 상태(localFavoriteIds)를 컴포넌트 로컬 상태로 직접 관리
  • API 통신, 상태, 필터링, UI 로직이 전부 한 파일에 몰려 있음

이 구조의 문제:

  • 다른 화면에서 같은 로직을 쓰고 싶어도 복붙밖에 답이 없음
  • UI와 데이터 로직이 섞여 있어서 테스트가 힘듦
  • 화면이 복잡해질수록 View 파일 길이가 끝없이 늘어남
  • 화면마다 상태 관리 방식이 제각각이라, 프로젝트가 커질수록 일관성이 깨짐

작은 예제 수준에서는 버틸 수 있지만, 실전에서는 바로 한계가 드러나는 구조다.


3. 리팩토링 후 전체 구조

리팩토링 포인트는 로직을 두 축으로 나누는 것:

  1. Composable (예: useCourses)

    • “강의 목록”이라는 기능 자체에 필요한 로직 담당
    • 강의 불러오기, 검색, 필터링, 로딩 상태, 에러 상태 등
  2. Pinia Store (예: useCourseStore)

    • “사용자 취향/설정”처럼 여러 화면에서 공유하는 전역 상태 담당
    • 즐겨찾기, 선호 난이도, 마지막으로 본 강의 등

그리고 화면 컴포넌트는 이렇게 단순해진다.

  • Composable과 Store를 가져와서
  • 템플릿에 바인딩 + 이벤트 연결만 담당

구조를 한 번 더 정리하면:

  • useCourses (Composable)
    → 강의 데이터 로딩/검색/필터링/로딩·에러 상태
  • courseStore (Pinia)
    → 즐겨찾기, 선호 난이도, 마지막 본 강의 등 전역 상태
  • View 컴포넌트
    → 화면 레이아웃, 버튼 클릭 시 어떤 action을 부를지 연결

Composition API의 장점을 그대로 살린 구조라고 보면 된다.


4. Composable: useCourses가 맡는 역할

useCourses() 같은 Composable이 하는 일은 보통 이렇게 정리된다.

  1. 강의 목록 상태 관리

    • courses, loading, error 같은 상태
  2. 데이터 로딩

    • fetchCourses() 내부에서 API 호출
    • onMounted 시 자동으로 한 번 호출
  3. 검색/필터링 로직

    • searchKeyword, levelFilter 등의 값에 따라
    • filteredCoursescomputed로 계산
  4. 화면에 종속되지 않는 로직만 모아두기

    • 어떤 화면에서 쓰든 똑같이 동작하도록 구성

이렇게 만들어두면 다음 화면들에서 전부 재사용 가능하다.

  • 강의 목록 페이지
  • 강의 검색 페이지
  • 강의 관리 페이지
  • 관리자용 강의 관리 대시보드

핵심은 UI와 연결되는 부분만 View에 남기고, 나머지 비즈니스 로직은 useCourses로 몰아넣는 것이다.


5. Pinia Store의 역할

Pinia는 “앱 전역에서 쓰는 상태”를 모아두는 창고 같은 역할을 한다.

Composable이 “기능 로직”이라면
Pinia는 “앱 전체에서 공유하는 데이터”라고 보는 게 이해하기 쉽다.

강의 예제를 기준으로 Pinia에서 관리하는 값 예시:

  1. favoriteIds
    → 즐겨찾기한 강의 ID 리스트
  2. preferredLevel
    → 사용자가 선호하는 난이도
  3. lastViewedCourseId
    → 마지막으로 열어본 강의 ID

이 값들의 공통점:

  • 하나의 화면에만 한정되지 않고, 여러 화면에서 함께 사용
  • 페이지를 이동해도 유지돼야 할 상태
  • UI에 직접 묶여 있지 않고, “사용자 상태/취향”에 가깝다

따라서:

Composable은 각 기능별 로직 단위,
Pinia는 여러 기능에서 공통으로 쓰는 전역 상태라고 이해하면 된다.


6. Composable + Pinia 조합의 효과

이렇게 구조를 나누면 실제 개발에서 체감되는 장점이 꽤 크다.

6-1. View 코드가 가벼워진다

  • API 호출, 로딩 상태, 필터링, 에러 처리 같은 건 다 useCourses와 Store로 이동
  • View는 const { courses, filteredCourses, fetchCourses } = useCourses() 같은 식으로 받아서 템플릿에 바인딩만 해 주면 된다.

→ 화면 파일이 “화면 코드”만 남아서 훨씬 읽기 편해진다.

6-2. 비즈니스 로직 재사용

  • 강의 목록을 가져오고 필터링하는 로직이 다른 페이지에서도 필요하면
    그냥 useCourses()만 다시 호출하면 된다.
  • 같은 패턴으로 useAuth(), useNotification(), useForm() 등 공통 기능을 계속 쌓아갈 수 있다.

6-3. 전역 상태 일관성

  • 즐겨찾기, 선호 난이도 같은 값은 Pinia에서 한 번만 관리
  • 헤더, 사이드바, 상세 페이지 등 어디에서 접근하든 동일한 store를 바라본다.

→ “어디서는 즐겨찾기 추가됐는데, 다른 곳에는 안 반영되는” 식의 꼬임을 줄일 수 있다.

6-4. UI 변경과 로직 변경이 분리

  • 화면 디자인을 갈아엎어도
    useCoursescourseStore는 그대로 둘 수 있다.
  • 새로운 UI 컴포넌트를 얹을 때도 기존 Composable/Store만 가져다 쓰면 된다.

6-5. 테스트가 쉬워진다

  • UI 없이 Composable만 떼어 테스트 가능
  • “API 응답이 이럴 때 필터링 결과가 이렇게 나와야 한다”는 식의 순수 로직 테스트가 가능하다.

7. 한 줄 요약

  • Composable
    → 기능 단위 로직 모듈화 (useCourses, useAuth, useForm 등)
  • Pinia
    → 여러 화면에서 공유하는 전역 상태 관리 (auth, favorites, 설정 등)
  • View 컴포넌트
    → UI 렌더링 + Composable/Store와의 바인딩

이 세 가지 역할을 명확하게 나누면,
페이지가 늘어나도 구조가 깨지지 않고,
로직 재사용과 유지보수가 훨씬 편해진다.

profile
개발자 희망자

0개의 댓글