Front-End(Web)/Vue

[Vue] Composition API

ttaeng_99 2022. 3. 12. 05:44
반응형

 

이번에 새로 맡게 된 프로젝트에서는, Nuxt.js(Vue2)에 Composition API를 적용하는 기술스택으로 개발이 되고있다.

 

Vue3가 정식 릴리스되면서 Vue2와의 큰 차이점중 하나인 Composition API에 대해 학습할 필요성을 느꼈다.

두 버전의 문법이 매우 상이했으며, 그래서 Composition API를 모르면 기존의 data(state), computed, methods, lifecycle 등의 기본적인 프로퍼티들마저도 원만하게 사용할 수 없었다.

 

본격적으로 개발을 시작하기 전에 Composition API의 차이점과 사용법 등을 공부하고자 하여 이 포스팅을 시작하게 되었다.


📗 Composition API 란?

Composition API 컴포넌트 내에서 사용하는 코드구조를 유연하게 구성하여 사용할 수 있도록 Vue3 버전에서 추가된 함수 기반의 API이다.

위 그림은 Vue2 의 Options 문법, 그리고 Vue3 에서 새로 출시된 Composition API 문법의 구성(관심사)을 비교한 것이다.

 

Options에서 data, computed, methods 등 데이터의 변화에 관련된 로직이 각각 흩어져 있다면, 

Composition API는 setup() 이라는 메서드 안에서 이들을 그룹핑하므로 데이터의 흐름을 쉽게 파악하고 유지보수가 용이해진다.

* 뒤에 설명하겠지만, setup() 메서드는 기존 Vue2 LifeCycle의 beforeCreate, created에 대응하는 일종의 훅 초기화 메서드다.

 

또한, 반복되는 코드들을 별도의 hooks로 모듈화하고 컴포넌트 내에서 import해서 사용할 수 있기 때문에 코드의 재사용성 면에서도 유리한 문법이다.

* Vue2의 믹스인(mixin)으로 컴포넌트 로직을 어느정도 재사용할 수 있었지만, 오버라이딩이나 다중 믹스인을 상속하면 컴포넌트 관리가 어려워지는 단점들도 존재했다.

 

 

- 설치 및 적용(Vue2)

올해 1월 21일, Vue3의 안정화 버전(3.2.28)이 정식 릴리즈되면서 Vue3를 기본적으로 지원하게 되었다.

여기서는 기본적으로 Composition API가 내장되어 있지만, 기존에 생성된 대부분의 Vue2 프로젝트에서도 선택적으로 사용할 수 있도록 플러그인을 지원했다.

 

// 설치
npm install @vue/composition-api
yarn add @vue/composition-api

먼저, @vue/composition-api 플러그인을 설치해준다.

 

// main.js
import Vue from 'vue'
import VueCompositionApi from '@vue/composition-api'
import App from './App.vue'

Vue.use(VueCompositionApi)

new Vue({
  render: h => h(App)
}).$mount('#app')

다음으로, main.js 진입점에 VueCompositionAPI 플러그인을 Vue.use()로 연동해준다. (Vue3에서는 불필요한 절차)

 

// /components/Example.vue

import { ref, reactive, defineComponent } from '@vue/composition-api';

export default defineComponent({
  name: 'Example',
  setup() {
    return { count: ref(0) }
  }
})

컴포넌트에서 다음과 같이 필요한 API들을 import해서 사용하면 된다.

defineComponent()는 setup() 내 인자들에서 Typescript 문법을 사용하기 위해 감싸주며, 이외에도 다양한 API들이 존재한다.

(공식 github 링크)

 

이제 Composition API의 주요 특징들을 알아보면서, 기존에 썼던 Options API의 기능들을 어떻게 구현할 수 있는지 알아보자!

 


📗 Composition API 주요 문법

- setup()

setup() 메서드는 컴포넌트가 생성되기 전에 props를 인식하면 실행되는 컴포넌트 옵션이며, Composition API의 진입점이다.

인자는 props, context(attrs, slots, emit, parent, root) 2가지를 받는다.

<template>
  <div class="home">
    <p>{{ name }} {{ age }}</p>
    <button @click="handleClick">click</button>
  </div>
</template>
<script>
export default {
  name: "HOME",
  setup() {
    // 반응형 아님
    let name = "nkh";
    let age = 29;

    const handleClick = () => {
      console.log(1);
    };
    return { name, age, handleClick };
  }
};
</script>

setup() 메서드를 통해 data(name, age) 및 methods(handleClick) 을 형성한 코드이다. 반환값들은 템플릿에서 액세스가 가능하다.

하지만, 지금 상태의 data는 Vue2의 data() 옵션과 같이 반응형이 아닌 상태이다.

* 반응형 데이터 : 값이 변경됨에 따라 이를 감지하고 해당 값에 종속된 작업(Side Effect)를 수행한다. (DOM 갱신)

 

 

- ref(), reactive() - 반응형 데이터

<template>
  <div class="home">
    <p>{{ person1.name }} {{ person1.value }}</p>
    <button @click="handleClick">click</button>
  </div>
</template>
<script>
import { ref, reactive } from "vue";

export default {
  name: "HOME",
  setup() {
    // 데이터를 ref, reactive로 감싸면 반응형으로 바뀝니다.
    const person1 = ref({ name: "nkh", age: 29 });
    const person2 = reactive({ name: "nki", age: 26 });

    const handleClick = () => {
      // ref로 감싼 값을 변경할 때는 value로 한번 들어가주고 값을 바꿉니다.
      person1.value.age = 30;

      // reactive는 바로 값을 바꿉니다.
      person2.age = 30;
    };

    // ref값은 return을 거치게되면 person1.value.age는 person1.age로 바뀝니다. (template에서는 person1.age로 사용합니다)
    return { person1, handleClick };
  }
};
</script>

이처럼, 데이터를 ref() 혹은 reactive() 메서드로 감싸면 반응형으로 바뀌게 된다.

또한, methods 에서 ref.value = newValue, reactive.key = newValue 등으로 데이터를 조작하는 것을 볼 수 있다.

 

ref() 와 reactive() 둘의 차이를 이해하는 것, 그래서 각각의 메서드를 적절하게 사용하는 것이 중요하다!

 

1) ref()

  • 모든 원시타입(Primitive) 값을 포함한 다양한 타입을 반응형으로 반영한다.
  • 단, 객체를 받을 경우 깊은(Deep) 감지가 불가하다. (그렇기에, reactive() 메서드가 필요한 것!)
  • 원본 값의 접근 및 변경은 ref.value 로 조작해야한다. (템플릿에서 액세스할 경우엔 ref 만으로도 가능)

 

2) reactive()

  • 오직 객체만을 인자로 받아 반응형을 반영한다. reactive()는 인자로 받은 객체와 완전히 동일한 프록시 객체(복사)를 반환한다.
  • 깊은(Deep) 감지를 적용하기 때문에, 객체가 중첩되어도 반응형 데이터를 쉽게 조작할 수 있다.
  • Vue2의 Vue.observable() 로 생성한 것과 동일. ES6의 프록시 객체와 다르게, 원본과 완전히 동일한 객체에 Vue 옵저버만 추가.

 

 

* isRef(), toRefs()

 

Composition API에서는 데이터가 ref인지 확인하는 isRef(), reactive 객체 프로퍼티들을 ref들로 바꿔주는 toRefs() 메서드도 지원한다.

// isRef
import { ref, reactive, isRef } from '@vue/composition-api'

const reactiveValue = reactive({ number: 10 })
const refValue = ref(20)

const printValue = data => console.log(isRef(data) ? data.value : data)
// toRefs
import { reactive, toRefs } from '@vue/composition-api'

const usePosition = () => {
  const pos = reactive({ x: 0, y: 0 })
  return toRefs(pos)
}

export default {
  setup() {
    const { x, y } = usePosition()
    
    return { x, y }
  }
}

이처럼, isRef() 는 ref와 reactive 데이터들을 각각 다르게 처리하기 위해 사용한다.

또한, reactive 객체는 구조분해 할당을 하면 반응성을 잃었지만, toRefs()로 감싸서 각각의 프로퍼티를 ref화 함에따라 반응형을 유지시킬 수 있다.

 

 

- props 받기

<template>
  <div>
    {{ post.title }}
    {{ post.body }}
  </div>
</template>
<script>
export default {
  props: {
    posts: { type: Object, required: true },
  },
  setup(props) {
    console.log(props.posts); // 받은 prop 사용가능
  }
};
</script>

자식 컴포넌트가 props를 활용할 경우, 위처럼 setup의 인자로 전달한 다음 내부에서 활용하면 된다.

 

 

- computed() - 계산된 속성

Vue2의 개념과 동일하게, 데이터를 기반으로 계산된 속성이며 읽기 전용인 특징이 존재한다.

<template>
 <div>
   <span>{{ value }} * 2</span>
   <span>{{ doubleValue }}</span>
 </div>
</template>

import { ref, computed } from '@vue/composition-api'

export default {
  setup() {
    const value = ref(10);
    const doubleValue = computed(() => value * 2)
  }
}

 computed() 메서드를 import 한 다음, 내부에 함수 형태로 계산된 반환값을 작성해주면 된다.

 

 

- watch() - 변경 감시

watch 역시 Vue2와 유사하게 사용할 수 있다. (deep, immediate 와 같은 옵션 역시 사용할 수 있다.)

import { ref, watch } from '@vue/composition-api'

const useList = () => {
  const list = ref([
  	{ id: 1, fruit: 'apple' }, 
    { id: 2, fruit: 'banana' },
    { id: 3, fruit: 'grape' },
  ])
  
  watch(list, (newVal, oldVal) => {
    // logic...
  }, { deep: true })
  
  return { list }
}

export default {
  setup() {
    const { list } = useList()
    
    setInterval(() => {
      list.value[0].name += '!'
    }, 3000)
  }
}

watch() 메서드를 import 한다. 인자는 감시할 데이터, 조작할 로직, 옵션 등을 받는다.

 

 

 

📗 Composition API의 LifeCycle

기존, Vue2 Options API는 좌측의 생명주기를, Vue3 Composition API는 우측으로 변경된 생명주기를 보인다.

create가 setup() 메서드로 대체된 점, 그리고 대부분의 생명주기에 on 수식어가 붙은 것이 전반적인 변화이다.

  • beforeCreate() -> setup() 대체
  • created() -> setup() 대체
  • beforeMount() -> onBeforeMount()
  • mounted() -> onMounsted()
  • beforeUpdate() -> onBeforeUpdate()
  • updated() -> onUpdated()
  • beforeDestroy() -> onBeforeUnmount()
  • destroyed() -> onUnmounted()
  • errorCaptured -> onErrorCaptured()

확실히, Vue3 Composition API의 형상이 React 함수 컴포넌트 및 Hooks API와 매우 유사한 부분이 많다는 것을 느꼈다.

함수형 프로그래밍에 기반한 문법이기도 하면서, data(state) 및 메서드(setter) 등을 함수화하여 재활용하는 부분이 대표적이다.

 

Composition API의 사용법은 막상 크게 어렵지 않았으며, 데이터에 반응형을 부여하는 ref와 reactive를 잘 쓰는 것이 중요할 것 같다.

(특히, 모든 데이터를 하나의 reactive 객체로 관리할지, 원시타입은 ref로 & 깊은 복사가 필요한 객체는 reactive를 사용할지는 각자의 기준과 취향에 따라 달라질 것 같다.)

 

나는 Vue가 협업하는데 있어 React에 비해 매우 유리하다고 생각했다. (data, computed, methods, LifeCycle 등의 포맷화)

하지만, 컴포넌트 내 로직이 커지면 이 데이터들의 흐름을 단번에 이해하기가 어려웠고 이 부분을 어느정도 해소시키기 위해 이 Composition API가 Vue3에 새로이 등장한 것이라 생각한다.

 

 

📎 출처

- [Composition API] Vue 공식문서 : https://v3.ko.vuejs.org/api/composition-api.html#getcurrentinstance  

- [Vue2 적용하기] @vue/composition-api github : https://github.com/vuejs/composition-api

- [Composition API 사용법] 기억보다 기록을 블로그 :  https://kyounghwan01.github.io/blog/Vue/vue3/composition-api/  

- [Composition API 주요 문법] geundung 님의 블로그 : https://geundung.dev/102

반응형