Composition API는 "무엇을 하느냐(기능)"로 코드를 구성하고, Options API는 "무엇이냐(역할)"로 구성합니다.

면접에서 "Composition API를 왜 쓰나요?"라는 질문에 "새로 나왔으니까요"가 아니라, 로직 재사용성과 코드 응집도 관점에서 답할 수 있어야 합니다.


Options API — 전통적인 방식

VUE
<script lang="ts">
import { defineComponent } from 'vue'

export default defineComponent({
  // 상태
  data() {
    return {
      count: 0,
      name: '심정훈'
    }
  },

  // 계산된 속성
  computed: {
    doubleCount(): number {
      return this.count * 2
    }
  },

  // 감시자
  watch: {
    count(newVal: number, oldVal: number) {
      console.log(`${oldVal} → ${newVal}`)
    }
  },

  // 생명주기 훅
  mounted() {
    console.log('마운트됨')
  },

  // 메서드
  methods: {
    increment() {
      this.count++
    },
    reset() {
      this.count = 0
    }
  }
})
</script>

<template>
  <p>{{ count }} (x2: {{ doubleCount }})</p>
  <button @click="increment">증가</button>
  <button @click="reset">리셋</button>
</template>

Composition API — 함수 기반 방식

VUE
<script setup lang="ts">
import { ref, computed, watch, onMounted } from 'vue'

// 상태
const count = ref(0)
const name = ref('심정훈')

// 계산된 속성
const doubleCount = computed(() => count.value * 2)

// 감시자
watch(count, (newVal, oldVal) => {
  console.log(`${oldVal} → ${newVal}`)
})

// 생명주기 훅
onMounted(() => {
  console.log('마운트됨')
})

// 메서드
const increment = () => {
  count.value++
}

const reset = () => {
  count.value = 0
}
</script>

<template>
  <p>{{ count }} (x2: {{ doubleCount }})</p>
  <button @click="increment">증가</button>
  <button @click="reset">리셋</button>
</template>

핵심 차이 비교

항목Options APIComposition API
코드 구성역할별 (data, methods, computed...)기능별 (관련 로직을 묶음)
로직 재사용Mixins (문제가 많음)Composables (깔끔)
TypeScript제한적 (this 타입 추론 어려움)네이티브 지원
학습 곡선낮음 (직관적 구조)중간 (반응형 이해 필요)
코드 크기작은 컴포넌트에서 간결큰 컴포넌트에서 유리
Tree-shaking제한적사용한 API만 번들에 포함

로직 재사용 — Mixins의 문제와 Composables

Mixins의 문제점 (Options API)

TYPESCRIPT
// counterMixin.ts
export const counterMixin = {
  data() {
    return { count: 0 }
  },
  methods: {
    increment() {
      this.count++
    }
  }
}

// 문제 1: 어디서 온 속성인지 불명확
// 문제 2: 이름 충돌 가능성
// 문제 3: 여러 Mixin 간 암묵적 의존성

Composables의 해결 (Composition API)

TYPESCRIPT
// useCounter.ts
import { ref } from 'vue'

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

  const increment = () => { count.value++ }
  const decrement = () => { count.value-- }
  const reset = () => { count.value = initialValue }

  return { count, increment, decrement, reset }
}
VUE
<script setup lang="ts">
import { useCounter } from './useCounter'

// 출처가 명확하고, 이름 충돌 없음
const { count, increment, reset } = useCounter(10)

// 여러 인스턴스를 독립적으로 사용 가능
const { count: count2, increment: increment2 } = useCounter(0)
</script>

<template>
  <p>카운터1: {{ count }}</p>
  <p>카운터2: {{ count2 }}</p>
</template>

코드 구성의 차이 — 큰 컴포넌트에서의 차이

Options API는 컴포넌트가 커질수록 관련 로직이 여러 옵션에 흩어집니다.

PLAINTEXT
Options API에서 "검색 기능" 코드가 흩어지는 모습:

data() {
  searchQuery: '',        ← 검색 관련
  users: [],              ← 사용자 관련
  searchResults: []       ← 검색 관련
}

computed: {
  filteredUsers() {},     ← 사용자 관련
  hasResults() {}         ← 검색 관련
}

watch: {
  searchQuery() {}        ← 검색 관련
}

methods: {
  fetchUsers() {},        ← 사용자 관련
  handleSearch() {},      ← 검색 관련
  clearSearch() {}        ← 검색 관련
}

Composition API에서는 관련 로직을 한곳에 모을 수 있습니다.

VUE
<script setup lang="ts">
// === 검색 기능 ===
const searchQuery = ref('')
const searchResults = ref([])
const hasResults = computed(() => searchResults.value.length > 0)
watch(searchQuery, (query) => { /* 검색 로직 */ })
const handleSearch = () => { /* ... */ }
const clearSearch = () => { /* ... */ }

// === 사용자 기능 ===
const users = ref([])
const filteredUsers = computed(() => { /* ... */ })
const fetchUsers = async () => { /* ... */ }
</script>

두 API 혼용 가능

Vue 3에서는 같은 프로젝트에서 두 API를 혼용할 수 있습니다.

VUE
<!-- Options API 컴포넌트에서 Composition API 사용 -->
<script lang="ts">
import { defineComponent, ref } from 'vue'
import { useCounter } from './useCounter'

export default defineComponent({
  // setup()은 Options API 안에서 Composition API를 쓸 수 있는 진입점
  setup() {
    const { count, increment } = useCounter()
    return { count, increment }
  },

  data() {
    return {
      name: '심정훈'
    }
  },

  computed: {
    greeting(): string {
      // setup에서 반환한 값도 this로 접근 가능
      return `안녕하세요, ${this.name}! 카운트: ${this.count}`
    }
  }
})
</script>

어떤 것을 선택해야 할까?

상황추천 API
새 프로젝트Composition API (script setup)
Vue 2에서 마이그레이션Options API 유지하며 점진적 전환
작은 컴포넌트어느 것이든
큰 컴포넌트 / 로직 재사용Composition API
TypeScript 사용Composition API
프로토타이핑 / 학습Options API (직관적)

면접 팁

  • "Composition API가 무조건 좋다"가 아니라, 프로젝트 규모와 팀 상황에 따라 선택 한다는 답이 좋습니다
  • Mixins의 세 가지 문제(출처 불명확, 이름 충돌, 암묵적 의존성)를 설명하고, Composables가 이를 어떻게 해결하는지 대비해서 설명하면 깊이 있어 보입니다
  • React의 Custom Hooks와 Vue의 Composables를 비교할 수 있으면 프레임워크 이해도를 보여줄 수 있습니다

요약

Options API는 역할별로 코드를 구성하여 직관적이고, Composition API는 기능별로 구성하여 로직 재사용과 TypeScript 지원이 뛰어납니다. 새 프로젝트에서는 Composition API(script setup)를 권장하지만, 프로젝트 상황에 맞는 선택이 가장 중요합니다.

댓글 로딩 중...