[Vue.js] 컴포저블(Composable)을 배우며 느낀 점
Vue 3에서 Composition API를 쓰다 보면 자연스럽게 접하게 되는 개념이 있다. 바로 컴포저블(Composable)이라는 것이다.
처음에는 단순히 setup()
안에서 로직을 작성하는 것만으로도 만족했는데, 컴포저블이라는 개념을 알게 되면서 코드 재사용과 유지보수 측면에서 큰 차이가 난다는 걸 배웠다.
컴포저블이란?
간단하게 말하자면, 반복적으로 사용하는 로직을 함수처럼 분리해서 가져다 쓰는 방식이다. React로 치면 custom hook과 매우 유사한 느낌이다.
예를 들어, 카운터 기능이 여러 컴포넌트에서 필요하다면 그 로직을 매번 setup()
안에 반복해서 작성할 필요 없이, 컴포저블로 하나 만들어두고 가져다 쓰면 된다.
예제 - useCounter
가장 많이 보는 기본 예제 중 하나. 나도 이걸 직접 구현해보면서 개념을 이해하게 됐다.
// composables/useCounter.js
import { ref } from 'vue'
export function useCounter(initialValue = 0) {
const count = ref(initialValue)
const increment = () => count.value++
const decrement = () => count.value--
return {
count,
increment,
decrement
}
}
컴포넌트 안에서는 이렇게 사용하면 된다:
<script setup>
import { useCounter } from '@/composables/useCounter'
const { count, increment, decrement } = useCounter(5)
</script>
<template>
<div>
<p>카운트: {{ count }}</p>
<button @click="increment">증가</button>
<button @click="decrement">감소</button>
</div>
</template>
정말 직관적이고, 컴포넌트에 꼭 필요한 데이터만 깔끔하게 쓰게 해주는 느낌이었다.
어떤 걸 컴포저블로 뽑으면 좋을까?
내가 느끼기엔 다음과 같은 상황일 때 컴포저블로 분리하면 좋았다.
- 2개 이상의 컴포넌트에서 동일한 로직을 사용
- API 호출 로직
- 다크모드, 윈도우 사이즈 등 환경 상태 관리
- 폼 validation, 입력값 처리
실전 예제 - useDarkMode
개인 프로젝트에서 실제로 다크모드를 적용하면서 만들었던 컴포저블이다. 문서 전체에 클래스를 토글하는 방식으로 구현했다.
// composables/useDarkMode.js
import { ref, watchEffect } from 'vue'
export function useDarkMode() {
const isDark = ref(false)
watchEffect(() => {
document.documentElement.classList.toggle('dark', isDark.value)
})
return { isDark }
}
사용할 땐 이렇게 쓰면 된다:
<script setup>
import { useDarkMode } from '@/composables/useDarkMode'
const { isDark } = useDarkMode()
</script>
<template>
<label>
<input type="checkbox" v-model="isDark" /> 다크 모드
</label>
</template>
마무리하며
Vue에서 컴포저블을 사용하면서 느낀 가장 큰 장점은 코드의 명확성과 재사용성이었다. 특히 프로젝트가 커질수록, 이런 구조적인 분리가 얼마나 중요한지 점점 체감하게 된다.
예전엔 mixin이나 이벤트 버스로 이런 걸 처리했다면, 이제는 useXXX
식으로 함수 하나 만들고 나눠쓰면 된다. 로직의 출처가 명확하고, 테스트도 편하고, 코드도 깔끔해졌다.
앞으로도 프로젝트를 하면서 자주 사용하게 될 것 같고, 더 다양한 패턴도 직접 만들어보며 익혀야겠다고 생각했다.