[Vue.js] Options API vs. Composition API 비교

Vue3 + Vite로 프론트엔드 개발을 하다가 지금까지 내가 써온 방식이 Options API라는 걸 알게됐다.

Options API는 vue2부터 쓰이던 전통적인 컴포넌트 구조인데 Vue에서 컴포넌트를 정의할 때 사용하는 방법이다. 특정 옵션을 미리 정의된 방식에 따라 구분해서 사용하는 게 특징인데, Vue 2 강의를 듣고 Vue 3 프로젝트를 하니까 틈만 나면 Vue 3에 맞는 Composition 방식을 사용하라는 추천을 받아서 리팩토링을 하기에 앞서 찾아보게 됐다.

1. Options API

(1) 주요 구성 요소

옵션 API에서는 각 기능이 미리 정해진 위치에 정의되는데, 컴포넌트를 구성할 때 다음과 같은 옵션들을 사용한다.

  1. data: 컴포넌트의 상태(데이터)를 정의하는 곳

     data() {
       return {
         count: 0,
       };
     }
    
    • data 함수는 컴포넌트의 초기 데이터를 반환하며, this로 접근할 수 있다.
  2. methods: 컴포넌트에서 실행될 함수를 정의하는 곳

     methods: {
       increment() {
         this.count++;
       },
     }
    
    • methods는 컴포넌트의 이벤트 처리, 데이터 조작 등을 위한 함수들을 포함한다.
  3. computed: 계산된 속성으로 종속된 데이터가 변경될 때만 재계산된다.

     computed: {
       doubleCount() {
         return this.count * 2;
       },
     }
    
    • computed는 데이터의 변화에 따라 자동으로 업데이트되는 속성을 정의한다.
  4. watch: 특정 데이터의 변화를 감지하고 이에 반응하는 메서드를 정의하는 곳

     watch: {
       count(newVal, oldVal) {
         console.log(`count가 ${oldVal}에서 ${newVal}로 변경되었습니다.`);
       },
     }
    
    • watch는 특정 데이터의 변화를 추적하고 그에 따른 처리를 할 수 있게 해준다.
  5. props: 부모 컴포넌트로부터 전달받는 데이터를 정의

     props: {
       title: String,
     }
    
    • props는 부모로부터 전달된 데이터를 사용할 수 있도록 해준다.
  6. emits: 컴포넌트에서 발생시킬 수 있는 이벤트를 정의한다.

     emits: ['custom-event']
    
    • 컴포넌트 내부에서 발생시킬 커스텀 이벤트를 정의하는 옵션이다.
  7. lifecycle hooks: 컴포넌트의 생명주기 동안 특정 시점에서 실행될 코드를 정의하는 곳이다.

     mounted() {
       console.log('컴포넌트가 마운트되었습니다.');
     }
    
    • Vue 컴포넌트의 생명주기 동안 호출되는 훅으로, created, mounted, updated, destroyed 등이 있다.

(2) 옵션 API 예시

export default {
  data() {
    return {
      count: 0,
    };
  },
  methods: {
    increment() {
      this.count++;
    },
  },
  computed: {
    doubleCount() {
      return this.count * 2;
    },
  },
  watch: {
    count(newVal, oldVal) {
      console.log(`count가 ${oldVal}에서 ${newVal}로 변경되었습니다.`);
    },
  },
  mounted() {
    console.log('컴포넌트가 마운트되었습니다.');
  },
};

(3) 장점

  1. 구조화된 코드: 각 기능이 명확하게 나뉘어져 있어서 코드의 가독성이 좋고, 초보자가 Vue를 배우기에 적합하다. → 리액트를 쓰다가 뷰를 처음 썼을 때 이 가독성이 진짜 큰 장점이라 생각했다.
  2. 명확한 기능 분리: 컴포넌트의 로직을 data, methods, computed, watch 등으로 구분해 작성하므로 어떤 부분이 어떤 기능을 하는지 쉽게 이해할 수 있다.
  3. 기존 Vue 개발자에게 익숙함: Vue 2 시절부터 사용된 방식이므로 많은 개발자들이 익숙하게 사용해왔다. → 그래서 인강에서도 이 방식으로 먼저 가르친 것 같다.

(4) 단점

  1. 재사용성 부족: 컴포넌트의 로직이 여러 옵션에 분산되기 때문에 코드 재사용성이 떨어지고, 같은 기능을 다른 컴포넌트에서 사용하려면 그 부분을 따로 빼내기 어려울 수 있다. → 이 부분에서 리액트가 코드 재사용하기에 더 편하다고 생각했었다.
  2. 복잡한 컴포넌트에서 비효율적: 컴포넌트가 복잡해질수록 data, methods, computed, watch 등이 길어지며, 로직이 이곳저곳에 흩어지게 되어 관리하기 어려워진다. → 결국에 코드가 길어지고 복잡해지면 가독성이 떨어지는 건 어쩔 수 없는 것 같다ㅜㅠ
  3. TypeScript와의 호환성 제한: TypeScript를 사용할 때, 옵션 API는 코드 추론이 복잡하고 TypeScript와의 완전한 통합이 어렵다.

2. 컴포지션 API

컴포지션(Composition) 은 소프트웨어 디자인에서 서로 독립적인 기능을 조합하여 더 복잡한 기능을 구성하는 방식을 의미한다. Vue.js에서 컴포지션 API(Composition API)는 Vue 3에서 도입된 새로운 API로, 옵션 API와 달리 상태와 로직을 더 유연하고 재사용성 높게 작성할 수 있는 방식이다.

(1) 주요 개념

  • setup() 함수: 컴포넌트가 생성될 때 호출되며, 이 함수 안에서 데이터, 메서드, 라이프사이클 훅 등을 정의한다.
  • reactive()와 ref(): Vue 3에서 데이터가 반응형이 되도록 하는 함수다. reactive()는 객체에 반응성을 부여하고, ref()는 기본 데이터 타입에 반응성을 부여한다.
  • watch()와 computed(): 특정 데이터가 변할 때 반응하거나 계산된 속성을 정의하는 방법이다.

(2) 컴포지션이 왜 필요할까?

  1. 로직의 재사용성 향상
    • 옵션 API에서는 데이터, 메서드, 라이프사이클 훅 등 서로 다른 곳에 정의된 기능들이 분산되어 있어서 컴포넌트의 복잡도가 증가할수록 재사용이 어려워진다.
    • 반면 컴포지션 API는 관련된 로직들을 setup() 함수 안에 모아서 하나의 기능 단위로 묶을 수 있기 때문에 재사용성이 뛰어나고, 이를 통해 여러 컴포넌트에서 같은 로직을 쉽게 사용할 수 있다.
  2. 복잡한 컴포넌트의 관리 용이
    • 옵션 API에서는 기능별로 데이터, 메서드, 라이프사이클 훅을 각각 나눠서 작성해야 하므로, 컴포넌트가 복잡해지면 코드가 매우 길어지고 관리가 어려워진다.
    • 컴포지션 API는 기능 중심으로 로직을 모듈화하여 관리할 수 있는데 여러 관련된 기능을 하나의 함수나 모듈로 나눠서 import하고 사용할 수 있기 때문에 복잡한 컴포넌트를 효율적으로 관리할 수 있다.
  3. 유연성과 확장성
    • 옵션 API에서는 Vue의 프레임워크에 맞춰 작성해야 했지만, 컴포지션 API는 자유롭게 함수와 라이브러리를 사용하여 다양한 방식으로 확장할 수 있다. 그리고 Vue 외부의 로직이나 라이브러리(ex. Pinia, WebSocket, Chart.js 등)를 더 자연스럽게 통합할 수 있다는 장점이 있다.
  4. 타입스크립트 지원
    • 컴포지션 API는 타입스크립트(TypeScript)와의 호환성이 훨씬 좋다. 함수 기반으로 상태와 메서드를 정의하기 때문에 타입을 쉽게 지정할 수 있고, 옵션 API보다 타입 추론이 정확하기 때문이다.

3. Options API와 Composition API의 차이 예시

(1) 옵션 API

export default {
  data() {
    return {
      count: 0,
    };
  },
  methods: {
    increment() {
      this.count++;
    },
  },
};

(2) 컴포지션 API

import { ref } from 'vue';

export default {
  setup() {
    const count = ref(0);
    const increment = () => {
      count.value++;
    };

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

4. 요약

  • 옵션 API는 Vue 2 시절부터 사용되었으며, 명확한 구조로 작성할 수 있지만 재사용성이 떨어진다.
  • 컴포지션 API는 Vue 3에서 새롭게 도입된 방식으로, 더 유연하고 재사용성이 높은 로직을 작성할 수 있다. 컴포넌트의 기능을 setup() 함수 내에서 하나로 묶어 관리할 수 있고, TypeScript와의 호환성도 훨씬 뛰어나다.
  • 컴포지션 API는 Vue 3에서 상태 관리와 로직을 더욱 모듈화하고 유연하게 관리할 수 있도록 도와준다.
  • Vue.js 생태계가 발전하면서 더 복잡한 기능을 구현할 때, 컴포지션 API는 더 나은 확장성과 관리성을 제공한다.

옵션 API 에서는 여러 옵션들 중 언제 뭘 사용해야 되는지 헷갈리는 일이 많았다. 코드 재사용에서 불편한 점도 느껴졌다.

그리고 Vue가 발전할수록 템플릿 모양은 유지하지만 상태 관리나 로직은 react hooks랑 비슷해져가는 것 같다는 생각이 든다.