[JavaScript][Vue.js] Composition API란? - Options API와의 차이부터 ref, computed, watch까지

2026. 8. 26. 10:29·개발/JavaScript
반응형

Vue 3에서 컴포넌트의 로직을 작성하는 대표적인 방식이 Composition API다.

Composition API는 컴포넌트에서 사용하는 데이터, 함수, 계산된 값, 감시 로직 등을 하나의 기능 단위로 묶어서 작성하는 방식이다.

Vue 2에서는 주로 Options API를 사용했지만, Vue 3에서는 Composition API와 <script setup> 방식이 기본적인 작성 방식으로 사용된다.


Composition API란?

Composition은 말 그대로 여러 로직을 조합(Composition)한다는 의미다.

예를 들어 카운터 기능을 만든다고 해보자.

카운터에는 최소한 다음과 같은 요소가 필요하다.

count 데이터
증가 함수
감소 함수
count를 이용한 계산값

Composition API에서는 이와 관련된 코드를 한곳에 모아 작성할 수 있다.

<script setup>
import { ref, computed } from 'vue'

const count = ref(0)

const increment = () => {
  count.value++
}

const decrement = () => {
  count.value--
}

const doubleCount = computed(() => {
  return count.value * 2
})
</script>

<template>
  <div>
    <p>현재 값: {{ count }}</p>
    <p>2배 값: {{ doubleCount }}</p>

    <button @click="increment">증가</button>
    <button @click="decrement">감소</button>
  </div>
</template>

카운터와 관련된 상태와 함수가 하나의 영역에 모여 있다.

[카운터 기능]

count
increment()
decrement()
doubleCount

이처럼 기능과 관련된 로직을 하나의 묶음으로 구성하는 것이 Composition API의 핵심이다.


기존 Options API와 무엇이 다른가?

Vue 2에서는 주로 Options API를 사용했다.

Options API는 컴포넌트의 코드를 역할별로 나눈다.

export default {
  data() {
    return {
      count: 0
    }
  },

  computed: {
    doubleCount() {
      return this.count * 2
    }
  },

  methods: {
    increment() {
      this.count++
    }
  },

  watch: {
    count(newValue) {
      console.log(newValue)
    }
  }
}

구조를 보면 다음과 같다.

data
→ 데이터

computed
→ 계산된 값

methods
→ 함수

watch
→ 데이터 감시

즉, 기능이 아니라 역할을 기준으로 코드를 나누는 방식이다.

컴포넌트의 규모가 작다면 구조가 명확하다.

하지만 하나의 기능이 커지면 관련 코드가 여러 영역에 흩어질 수 있다.

예를 들어 로그인 기능을 만든다고 해보자.

data
 └─ user

computed
 └─ isLoggedIn

methods
 ├─ login()
 └─ logout()

watch
 └─ user 감시

로그인이라는 하나의 기능을 수정하기 위해 data, computed, methods, watch 영역을 각각 확인해야 한다.

Composition API는 이를 기능 중심으로 모은다.

const user = ref(null)

const isLoggedIn = computed(() => {
  return user.value !== null
})

const login = () => {
  user.value = {
    name: 'Kim'
  }
}

const logout = () => {
  user.value = null
}

watch(user, () => {
  console.log('로그인 상태 변경')
})

구조를 보면 다음과 같다.

[로그인 기능]

user
isLoggedIn
login()
logout()
watch()

자료에서도 Options API는 data, methods, computed와 같은 정해진 옵션에 코드를 나누는 방식이고, Composition API는 관련 있는 데이터와 함수를 논리적인 기능별로 묶는 방식으로 구분한다.

 

정리하면 다음과 같다.

구분  Options API Composition API
코드 구성 기준 역할별 기능별
상태 선언 data() ref(), reactive()
함수 methods 일반 JavaScript 함수
계산값 computed 옵션 computed()
감시 watch 옵션 watch()
작성 방식 <script> 주로 <script setup>
Vue 기준 Vue 2 중심 Vue 3 표준

<script setup>은 무엇인가?

Composition API는 Vue 3.2부터 <script setup> 안에서 간결하게 작성할 수 있다.

Vue의 Single File Component는 기본적으로 다음 구조를 가진다.

<script setup>
  // JavaScript
</script>

<template>
  <!-- HTML -->
</template>

<style scoped>
  /* CSS */
</style>

각 영역의 역할은 다음과 같다.

<script setup>
→ 데이터
→ 함수
→ 비즈니스 로직

<template>
→ 화면 구조

<style>
→ 디자인

자료에서도 Vue 3의 SFC는 <script setup>, <template>, <style>의 구조로 구성되며, <script setup>에서 데이터와 함수 등의 기능 로직을 작성한다고 설명한다.

예를 들어 간단한 카운터는 다음과 같이 작성할 수 있다.

<script setup>
import { ref } from 'vue'

const count = ref(0)

const increment = () => {
  count.value++
}
</script>

<template>
  <button @click="increment">
    {{ count }}
  </button>
</template>

Options API처럼

export default {
  data() {},
  methods: {}
}

구조를 만들 필요 없이 일반 JavaScript처럼 변수와 함수를 선언한다.


Composition API의 핵심 함수

Composition API는 Vue에서 제공하는 여러 함수를 조합해서 사용한다.

대표적인 기능은 다음과 같다.

ref()
reactive()
computed()
watch()
watchEffect()

onMounted()
onUpdated()
onUnmounted()

자료에서도 Vue 3 내장 기능을 Reactive State, Computed & Watchers, Lifecycle Hooks 등으로 구분하고 있다.


ref()

ref()는 값을 반응형 상태(Reactive State)로 만드는 함수다.

import { ref } from 'vue'

const count = ref(0)

일반 JavaScript 변수와 달리 ref()로 만든 데이터는 값이 변경되면 Vue가 그 변화를 감지하고 화면을 업데이트한다.

<script setup>
import { ref } from 'vue'

const count = ref(0)

const increment = () => {
  count.value++
}
</script>

<template>
  <p>{{ count }}</p>

  <button @click="increment">
    증가
  </button>
</template>

<script setup>에서는 다음처럼 .value를 사용한다.

count.value++

하지만 <template>에서는 .value가 필요 없다.

<p>{{ count }}</p>

즉 다음과 같다.

<script setup>

count.value


<template>

count

자료에서도 ref()는 원시 타입뿐 아니라 배열과 객체도 반응형 상태로 만들 수 있으며, script에서는 .value, template에서는 .value 없이 사용한다고 설명한다.


reactive()

reactive() 역시 반응형 데이터를 만드는 함수다.

주로 객체나 배열과 같은 참조형 데이터를 다룬다.

import { reactive } from 'vue'

const user = reactive({
  name: '홍길동',
  age: 20
})

ref()와 달리 .value를 사용하지 않는다.

user.age++

Template에서도 동일하다.

<p>{{ user.name }}</p>
<p>{{ user.age }}</p>

다만 reactive() 객체 자체를 새로운 객체로 재할당하면 반응성 연결이 끊어질 수 있다는 점을 주의해야 한다.

예를 들어 다음과 같은 방식은 피해야 한다.

let state = reactive({
  count: 0
})

state = {
  count: 10
}

대신 내부 속성을 변경한다.

state.count = 10

또는 객체와 배열까지 ref()로 통일해서 사용하는 방식도 가능하다.

const state = ref({
  count: 0
})

state.value.count = 10

computed()

computed()는 기존 반응형 데이터를 이용해서 새로운 값을 계산할 때 사용한다.

예를 들어 상품 가격과 수량이 있다고 해보자.

const price = ref(10000)
const quantity = ref(3)

총 가격은 다음과 같이 계산할 수 있다.

const totalPrice = computed(() => {
  return price.value * quantity.value
})

Template에서는 다음과 같이 사용한다.

<p>총 가격: {{ totalPrice }}원</p>

구조는 다음과 같다.

price
   +
quantity
   ↓
computed
   ↓
totalPrice

price 또는 quantity가 변경되면 totalPrice도 자동으로 다시 계산된다.

computed()의 중요한 특징은 캐싱(Caching)이다.

const doubleCount = computed(() => {
  return count.value * 2
})

count 값이 바뀌지 않았다면 이전 계산 결과를 재사용한다.

일반 함수가 컴포넌트가 다시 렌더링될 때마다 재실행되는 반면, computed()는 의존하는 반응형 데이터가 변경됐을 때만 다시 계산된다.

그래서

값을 만들어서 화면에 보여주고 싶다
→ computed

라고 생각하면 된다.


watch()

watch()는 특정 반응형 데이터가 변경되는 것을 감시하고, 변경되었을 때 특정 작업을 수행한다.

import { ref, watch } from 'vue'

const city = ref('서울')

watch(city, (newValue, oldValue) => {
  console.log(oldValue)
  console.log(newValue)
})

city가

서울

에서

수원

으로 변경되면 watch()의 callback이 실행된다.

city 변경
   ↓
watch 감지
   ↓
callback 실행

watch()는 다음과 같은 상황에서 활용할 수 있다.

검색어 변경
→ API 다시 호출

선택 도시 변경
→ 날씨 데이터 다시 조회

로그인 상태 변경
→ 사용자 정보 저장

설정값 변경
→ LocalStorage 저장

watch()를 반응형 데이터의 변경을 감지한 뒤 서버 통신이나 데이터 저장 같은 후속 로직을 실행하기 위한 함수로 설명할 수 있다.


watchEffect()

watchEffect() 역시 데이터를 감시하지만 watch()와 차이가 있다.

watch()는 감시할 변수를 직접 지정한다.

watch(city, () => {

})

반면 watchEffect()는 내부에서 사용하는 반응형 데이터를 Vue가 자동으로 추적한다.

watchEffect(() => {
  console.log(city.value)
  console.log(age.value)
})

Vue가 내부 코드를 확인한 뒤

city
age

를 자동으로 감시한다.

또한 watchEffect()는 컴포넌트가 생성될 때 최초 한 번 즉시 실행된다.

 

watchEffect()는 감시 대상을 별도로 지정하지 않고 함수 내부에서 접근한 반응형 데이터를 자동으로 추적하며, 최초 실행 시에도 즉시 실행된다.

 

차이를 단순하게 보면 다음과 같다.

구분 watch() watchEffect()
감시 대상 직접 지정 자동 추적
이전 값 사용 가능 사용 불가
현재 값 사용 가능 내부에서 직접 사용
최초 실행 기본적으로 X O

Lifecycle Hook

컴포넌트에도 생성부터 제거까지의 생명주기가 있다.

생성
 ↓
Mount
 ↓
Update
 ↓
Unmount

Composition API에서는 이 시점에 맞춰 특정 함수를 실행할 수 있다.

대표적으로 다음 세 가지가 있다.

onMounted()
onUpdated()
onUnmounted()

onMounted()

컴포넌트가 실제 DOM에 연결된 이후 실행된다.

import { onMounted } from 'vue'

onMounted(() => {
  console.log('화면에 컴포넌트가 생성됨')
})

초기 API 요청 등을 처리할 수 있다.

onMounted(async () => {
  const response = await axios.get('/api/users')
})

onUpdated()

반응형 데이터가 변경되고 화면이 다시 그려진 이후 실행된다.

onUpdated(() => {
  console.log('화면 업데이트 완료')
})

onUnmounted()

컴포넌트가 화면에서 제거된 이후 실행된다.

onUnmounted(() => {
  console.log('컴포넌트 제거 완료')
})

타이머나 이벤트 리스너를 정리할 때 사용할 수 있다.

onUnmounted(() => {
  clearInterval(timerId)
})

Mount 시점은 초기 DOM 조작과 API 호출, Unmount 시점은 타이머 및 이벤트 리스너 정리 등에 활용하는 단계이다.


하나의 기능으로 묶어보면

Composition API의 장점은 각각의 함수 자체보다 이 함수들을 하나의 기능 단위로 조합할 수 있다는 점에 있다.

예를 들어 상품 검색 기능을 만든다고 해보자.

<script setup>
import { ref, computed, watch } from 'vue'

const searchKeyword = ref('')

const products = ref([
  { id: 1, name: '노트북' },
  { id: 2, name: '키보드' },
  { id: 3, name: '마우스' }
])

const filteredProducts = computed(() => {
  return products.value.filter((product) =>
    product.name.includes(searchKeyword.value)
  )
})

watch(searchKeyword, (newKeyword) => {
  console.log('검색어 변경:', newKeyword)
})

const clearSearch = () => {
  searchKeyword.value = ''
}
</script>

검색 기능과 관련된 코드가 한곳에 정리된다.

[상품 검색 기능]

searchKeyword
products
filteredProducts
watch()
clearSearch()

이것이 Composition API가 말하는 Composition이다.

각 기능에 필요한 상태와 로직을 하나의 묶음으로 구성하는 것이다.


Composable

Composition API의 기능 묶음은 별도의 JavaScript 함수로 분리할 수도 있다.

이를 Composable이라고 한다.

예를 들어 여러 컴포넌트에서 공통으로 사용하는 카운터 기능이 있다고 해보자.

// useCounter.js

import { ref } from 'vue'

export function useCounter() {
  const count = ref(0)

  const increment = () => {
    count.value++
  }

  const decrement = () => {
    count.value--
  }

  return {
    count,
    increment,
    decrement
  }
}

컴포넌트에서는 필요한 기능을 가져온다.

<script setup>
import { useCounter } from './useCounter'

const {
  count,
  increment,
  decrement
} = useCounter()
</script>

하나의 기능을 별도의 JavaScript 모듈로 분리했기 때문에 여러 컴포넌트에서 재사용할 수 있다.

useCounter()

        ↓

Component A
Component B
Component C

Composable을 Vue의 반응형 상태와 로직을 묶어서 재사용할 수 있도록 만든 함수로 설명한다.


Composition API 전체 구조

지금까지 살펴본 내용을 하나로 정리하면 다음과 같다.

            Composition API

                  │
       ┌──────────┼──────────┐
       │          │          │
       ↓          ↓          ↓

   Reactive    Computed    Watcher
    State

       │          │          │
       ↓          ↓          ↓

    ref()      computed()   watch()
  reactive()              watchEffect()

                  │
                  ↓

          Lifecycle Hooks

         onMounted()
         onUpdated()
         onUnmounted()

                  │
                  ↓

             Component

결국 Composition API는 특정 함수 하나를 의미하는 것이 아니다.

ref
+
reactive
+
computed
+
watch
+
Lifecycle Hook
+
일반 JavaScript 함수

같은 기능들을 조합해서 하나의 컴포넌트 로직을 구성하는 방식 전체를 의미한다.


정리

Options API와 Composition API의 가장 중요한 차이는 코드를 어떤 기준으로 구성하느냐다.

Options API는 역할을 기준으로 나눈다.

data
methods
computed
watch

Composition API는 기능을 기준으로 묶는다.

로그인 기능

user
isLoggedIn
login()
logout()
watch()

그리고 Vue 3에서는 다음과 같은 형태로 구성한다.

<script setup>
import {
  ref,
  computed,
  watch,
  onMounted
} from 'vue'

const data = ref(...)

const result = computed(() => {
  ...
})

watch(data, () => {
  ...
})

const functionName = () => {
  ...
}

onMounted(() => {
  ...
})
</script>

핵심을 한 문장으로 정리하면 다음과 같다.

Composition API는 Vue 컴포넌트에서 사용하는 상태, 함수, 계산 로직, 감시 로직 등을 기능 단위로 조합해서 작성하는 Vue 3의 코드 구성 방식이다.

Composition API를 이해한 뒤에는 ref(), reactive(), computed(), watch()를 각각 구체적으로 정리하면 Vue의 반응형 데이터 처리 구조를 이해하기 쉬워진다.

반응형

'개발 > JavaScript' 카테고리의 다른 글

[JavaScript][Vue.js] Props와 Emits 정리 - 부모·자식 컴포넌트 통신  (0) 2026.08.26
[JavaScript][Vue.js] Component Lifecycle과 Lifecycle Hook 정리  (0) 2026.08.26
[JavaScript][Vue.js] Vue Directive 문법 정리 - v-bind부터 v-if, v-for까지  (1) 2026.08.24
[JavaScript][Vue.js] Vue 기본 개념 이해하기  (0) 2026.08.24
[JavaScript] 비동기 처리 이해하기 - Callback부터 Promise, async/await까지  (0) 2026.08.24
'개발/JavaScript' 카테고리의 다른 글
  • [JavaScript][Vue.js] Props와 Emits 정리 - 부모·자식 컴포넌트 통신
  • [JavaScript][Vue.js] Component Lifecycle과 Lifecycle Hook 정리
  • [JavaScript][Vue.js] Vue Directive 문법 정리 - v-bind부터 v-if, v-for까지
  • [JavaScript][Vue.js] Vue 기본 개념 이해하기
danieLee
danieLee
개발일지
  • danieLee
    Code log
    danieLee
  • 전체
    오늘
    어제
    • 분류 전체보기 (77)
      • 개발 (76)
        • C++ (3)
        • java (6)
        • JavaScript (9)
        • python (0)
        • AWS (2)
        • Docker (6)
        • git (0)
        • 백엔드 (4)
        • Spring (7)
        • Django (2)
        • AI (3)
        • 코테 준비 (13)
        • 알고리즘 (6)
        • SKALA 4기 (13)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    4기
    개념
    프론트
    Ai
    skala
    백엔드
    spring
    코테
    vue.js
    프로그래머스
    API
    js
    java
    알고리즘
    JavaScript
    파이썬
    개발자
    개발
    서버
    대학생
  • 최근 댓글

  • 최근 글

  • 반응형
  • hELLO· Designed By정상우.v4.10.1
danieLee
[JavaScript][Vue.js] Composition API란? - Options API와의 차이부터 ref, computed, watch까지
상단으로

티스토리툴바