프론트엔드 프레임워크 중 하나인 Vue는 현재 2.6.x 버전이고, 3.x.x 메이저 버전 업데이트를 앞두고 있다. Vue-next에서 확인할 수 있는 Vue3의 주요 변경점은 컴파일러 개선, 타입스크립트 지원 강화, 그리고 composition api 등이 있다. composition api의 경우 Vue 2.x버전에서 사용할 수 있도록 배포되어있다. 이 글에서는 composition api 사용법을 소개하는게 아닌, 짤막하게 사용해본 후기 정도를 적어볼 예정이다.

composition api란

공식 사이트 설명에 따르면, composition api는 코드 재사용, 코드 로직 모으기가 개선되었다고 한다. 기존에 mixin 기능으로 코드 재사용이 가능했는데, 이는 namespace가 충돌 시 병합되는 과정이 매끄럽지 않은 등 아쉬운 점이 있다. 그리고 기존 반응성 있는 값, lifecycle 훅, methods 등을 한 컴포넌트에 몰아넣었는데, 관련된 로직임에도 코드가 중구난방으로 흩어져 있어서 컴포넌트의 코드가 많을 경우 가독성이 좋지 않았다. 구체적으로 어떻게 바뀐 걸까? 코드로 살펴보자.

기존 방식

<template>
  <button @click="increment">
    Count is: {{ count }}, double is: {{ double }}
  </button>
</template>

<script>
export default {
  data() {
    return {
      count: 0
    }
  },
  computed: {
    double() {
      return this.count * 2
    }
  },
  methods: {
    increment() {
      this.count++
    }
  }
}
</script>

지금 흔히 사용하고 있는 Vue 컴포넌트 코드이다. 모든 반응성 있는 값들은 data안에 들어있고, 다른 반응성 있는 값에 의존하는 값은 모두 computed에 들어있다. methods도 마찬가지이다. 현재 코드만 있을 때는 별 문제가 없어 보인다. 하지만 다음 예시처럼 다른 로직이 추가된다면 어떨까?

<template>
  <div>
    <button @click="increment">
      Count is: {{ count }}, double is: {{ double }}
    </button>
    <input type="text" v-model="inputValue" />
    reversed : {{ reversedInputValue }}
    <button @click="openModal">open modal</button>
    <Modal v-show="visibleModal" />
  </div>
</template>

<script>
import Modal from '~/components/Modal'

export default {
  components: {
    Modal
  },
  data() {
    return {
      count: 0,
      visibleModal: false,
      inputValue: ''
    }
  },
  computed: {
    double() {
      return this.count * 2
    },
    reversedInputValue() {
      return this.inputValue.split('').reverse().join('')
    }
  },
  methods: {
    increment() {
      this.count++
    },
    openModal() {
      this.visibleModal = true
    }
  }
}
</script>

위 컴포넌트는 count, input, modal 3가지 로직이 각각 data, computed, methods로 분리되어있다. 경험상 라인 수가 길어지면 한번에 로직 찾기도 힘들고, 스크롤을 많이 해야했던 경우가 빈번했다.

이 코드를 composition api를 사용하면 어떻게 바뀔까?

composition api 방식

<template>
  <div>
    <button @click="increment">
      Count is: {{ count }}, double is: {{ double }}
    </button>
    <input type="text" v-model="inputValue" />
    reversed : {{ reversedInputValue }}
    <button @click="openModal">open modal</button>
    <Modal v-show="visibleModal" />
  </div>
</template>

<script>
import { ref, computed } from '@vue/composition-api'
import Modal from '~/components/Modal'

const useCount = () => {
  const count = ref(0)
  const double = computed(() => count.value * 2)

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

  return { count, double, increment }
}

const useHandleModal = () => {
  const visibleModal = ref(false)

  const openModal = () => {
    visibleModal.value = true
  }

  return { visibleModal, openModal }
}

const useHandleInput = () => {
  const inputValue = ref('')
  const reversedInputValue = computed(() => inputValue.value.split('').reverse().join(''))

  return { inputValue, reversedInputValue }
}

export default {
  components: {
    Modal
  },
  setup() {
    const { count, double, increment } = useCount()
    const { visibleModal, openModal } = useHandleModal()
    const { inputValue, reversedInputValue } = useHandleInput()
    return {
      count,
      double,
      increment,
      visibleModal,
      openModal,
      inputValue,
      reversedInputValue
    }
  }
}
</script>

각 로직을 함수로 묶어서 작성하고, 초기 설정을 하는 setup() 훅에서 초기화를 진행했다. 공통 로직의 코드가 한곳으로 모인 것이다.

setup 훅은 기존 beforeCreatecreated가 합쳐진 개념이다. ref는 새로 생긴 반응성 있는 값을 만드는 기능을 한다.

확실히 코드 응집도와 재사용성이 올라갈 것으로 기대한다.

내 생각 및 도입 계획

일단 처음으로 composition api를 보고 React의 hook이 생각났다. 각 로직 함수 내에서 ref 값의 접근할 때마다 .value를 명시적으로 사용해야 한다는 점은 아쉽다. 하지만 기존에는 반응성 있는 값 또는 메소드에 접근하려면 this.을 모두 해야하기 때문에 결과적으로는 더욱 맘에 든다.

composition api에는 반응성 있는 값으로 만들 때 ref 뿐만이 아니라 react라는 함수도 제공되는데, 이 두가지를 어떻게 구분해서 써야하지는지도 살펴봐야겠다.

그리고 내 개발 환경의 경우 서버 사이드 렌더링 프레임워크 Nuxt를 사용하고 있다. Nuxt는 컴포넌트에 data, created, mounted 속성 뿐만 아니라 fetch, asyncData 등도 함께 제공한다. 이것이 composition api와 잘 결합이 되는 시점에 도입할 생각이다.