blog.coinsect.io
🇰🇷
원문
마크다운
2024-10-24
32
공유

Vue 컴포넌트는 어떻게 작성하는게 현시점 de facto일까?

## Options API Vue 2 에서는 아래와 같이 작성하는 Options API가 주류였다. ```javascript <script> export default { props: { // 부모로부터 넘겨받는 값들 }, methods: { // 메소드들 }, computed: { // 계산된 값들 }, data: () => { // 데이터 }, mounted() { }, } </script> ``` 여기에는 다음과 같은 단점들이 있었다. - 컴포넌트 내에 특정 비즈니스 로직을 구현하는 경우, 이 로직들이 각각 `methods`, `computed`, `data` 등 각 속성별로 분리되어, 덩어리째 재사용이 어려움. ![](https://d1085v6s0hknp1.cloudfront.net/boards/coinsect_blog/ea321963-4a64-45f1-80aa-84183bf26c73_image.png) 이 그림은 Options API의 단점을 명확하게 보여준다. - `this`의 사용. 나는 코딩을 할 때 this나 self 등의 개념을 사용하지 않는 것이 좋다고 생각한다. Python, Java, Ruby 등 이것이 혼란을 줄 여지가 없는 언어들의 경우는 모르겠으나, JS의 경우는 그것이 호출되는 컨텍스트에 따라 가리키는 대상이 바뀔 수 있기 때문에 (e.g. [화살표 함수는 컨텍스트를 만들지 않음](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Functions/Arrow_functions)) 객체 내에서 자기 자신을 인용할지라도 인스턴스의 이름을 항상 적는 것이 좋다고 생각한다. 이것은 추후 코드를 리팩토링하며 코드를 이리저리 옮기는 과정에서 this가 무엇을 가리키는지 신경 쓸 필요가 없도록 만들어준다. 또, computed같은 경우 정의할 때는 함수로 정의하나 호출할 때는 그냥 `this.someVal`과 같이 '값'을 호출하는 방식으로 사용하게 되니, 사실 초심자 입장에서는 혼란스럽다. Options API는 Composition API가 등장하기 전의 과도기적 형태였다고 생각한다. ## Composition API Vue 3부터는 Composition API가 공식적으로 권장사항이 되었다. ```javascript <script> import { ref, computed, onMounted } from 'vue' export default { setup() { const myData = ref() const myComputedVal = computed(() => { ... }) const init = () => {} onMounted(init) }, } ``` Composition API는 이처럼 'vue' 패키지에서 반응형 변수를 선언할 수 있도록 해주는 `ref`, `computed`나 `onMounted`와 같은 lifecycle hook을 import하여 `setup()` 안에 구현하는 방식이다. 이것의 가장 큰 장점은 `ref`, `computed`, `onMounted`, `watch` 등의 기능들이 `vue` 패키지를 통해 제공되기 때문에, `.vue` 파일 뿐 아니라 일반적인 `.js` 파일들에서도 사용할 수 있게 되었다는 것이다! 이처럼 재사용 가능한 로직들의 구현을 `.vue` 파일 외부로 빼낼 수 있게 되었기 때문에, `.vue` 파일들은 좀 더 `<template>`과 `<style>` 등 눈에 보이는 부분에 집중하고, 로직들은 외부로 묶는 형태의 프로젝트 구성이 용이해졌다. 누가봐도 리액트의 훅에서 영감을 받은 것이 명확해보이는 이 스펙은 Vue에서는 [composable](https://v3-docs.vuejs-korea.org/guide/reusability/composables.html)이라 불린다. ```javascript // my-hook.js import { computed } from 'vue' export const useMyHook = () => { const todayISO8601 = computed(() => (new Date()).toISOString().split('T')[0] ) return { todayISO8601, } } // MyComponent.vue import useMyHook from '@/hooks/my-hook' <template> <div>{{ todayISO8601 }}</div> </template> <script> export default { setup() { const { todayISO8601 } = useMyHook() return { todayISO8601, } }, } </script> ``` 상태관리나 생명주기 등과 무관한 단순 기능에 해당하는 함수들은 보통 helper라고 구분하면 되겠고, `ref`, `computed` 또는 lifecycle 함수나 상태 등이 엮이는 함수들은 composable(= hook)로 구분할 수 있다. ## [`<script setup>`](https://vuejs.org/api/sfc-script-setup) 그런데 `<script>` 내부에서 매번 `export default { setup() {} }`을 작성하고, 템플릿에 노출할 값들을 명시적으로 `return`하는 것도 사실 귀찮은 보일러플레이트이다. 그래서 그것에 대한 설탕으로, `<script setup>`이라는 것이 존재한다. ```javascript <template> <div>{{ todayISO8601 }}</div> </template> <script setup> import useMyHook from '@/hooks/my-hook' const props = defineProps(...) const emit = defineEmits([...]) const { todayISO8601 } = useMyHook() </script> ``` 위 코드에서 알 수 있듯이, 번거로운 보일러플레이트가 꽤나 간소화된다. 또한 선언한 변수나 함수들은 별도의 `return` 없이 자동으로 템플릿에 노출되어 사용할 수 있게 된다. 그리고 prop, emit은 defineProps, defineEmits를 통해 정의하는데, 이런 함수들은 딱히 명시적으로 import하지 않아도 된다. ## Typescript 처음 프로젝트를 생성할 때부터 Typescript 기반으로 생성했다면 바로 사용할 수 있고, 기존 프로젝트에서 추가하고 싶은 경우라면 터미널에서 간단히 ``` vue add typescript ``` 를 하면 된다. (`vue-cli`가 글로벌로 설치되어 있는 경우 가정) 타입스크립트를 설치했다면 이후부터는 `<script lang="ts">`과 같이 `lang="ts"`만 붙이면 된다. 물론 당연하게도 `<script setup lang="ts">`도 가능하다. ## 내가 새로 Vue 프로젝트를 만든다면? - Vite - Typescript - script setup - Vuex (X) [Pinia (O)](https://pinia.vuejs.kr/) 요렇게 scaffolding 할 것 같다. 현 회사에서의 3년 넘은 Vue 3 프로젝트 역시 최근에 script setup + typescript로의 전환을 마쳤다. 너무나 작업량이 많아 엄두를 내지 못 하고 있었는데 github copilot의 도움으로 비교적 빠르게 마칠 수 있었다.
0