ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Vue] Composition API
    Front-End(Web)/Vue 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

    반응형
Designed by Tistory.