시작으로

이직한 회사에서 사용하는 웹 기술 스택이 Vue3이다. 전 회사에서 Vue2를 사용해봤지만 스터디할 시간이 부족하여 실무하면서 그때그때 찾아보면서 스터디를 했다. 그래도 Vue3가 나온 이유의 대해서 정확하게 알지 못하기 때문에 컨셉, 새로운 기능, 주의해야할 점들을 정리해보려고 한다. 공식문서에 가이드를 보고 리스트업을 시작했다. 모든 기능을 다루기 보다 현업에서 사용하면서 정리하고 싶었던 내용을 중심으로 시작하려고 한다.

Vue2의 문제점 개선

기존 Vue2의 문제점이라고 하면 크게 두 가지로 나눌 수 있다.

컴포넌트 코드 재사용성

요즘 프론트엔드 프레임워크들은 모두 컴포넌트 기반의 UI 개발 방식을 추구한다. 그 이유는 컴포넌트 방식으로 개발하면 코드를 재사용할 수 있고 디버깅도 수월하게 할 수 있기 때문이다. 이전 버전의 Vue는 믹스인(Mixins), 슬록(slots), 하이 오더 컴포넌트(High Order Components) 3가지 방식을 통해 컴포넌트 코드를 재사용할 수 있었다. 보통 믹스인을 많이 사용했었는데, 프로젝트 몸집이 커지고 다중으로 상속하게되면 컴포넌트 관리가 어려워졌다. 또한, 믹스인은 컨벤션이 굉장히 중요한 문법이다. 그리고 매개변수를 믹스인을 통해 전달할 수 없기 때문에 코드의 유연성이 떨어졌다. 이번 Vue3의 컴포지션 API를 사용하게 되면 인스턴스의 특정 기능 단위로 모듈화된 로직을 여러 컴포넌트에서 재사용할 수 있다. 대규모 프로젝트에서 로직의 유연성을 높여준다.

타입스크립트 지원

Vue2로 개발을 시작 할 당시, 타입스크립트를 같이 사용하는 것을 권장하지 않았었다. 그 이유는 완벽하게 호환되지 않았다. 그 이유는 Vue.js가 기존의 옵션 API를 중심으로 객체 구조 방식을 사용하기 때문에 타입스크립트를 온전히 사용하기에 한계가 있었다. 타입스크립트는 타입을 명시적으로 선언하지 않아도 추론이 가능해야 하는데 객체 구조 특성상 개발자가 일일이 타입을 정의해 줘야 하기 때문에 개발하는데 어려운 상황이 많았다. 기본적으로 타입스크립트는 객체 구조보다 함수 구조에서 더 많은 이점을 가져올 수 있다. 이번 Vue3가 새롭게 등장하면서 적극적으로 타입스크립트를 지원하기 위해 코드베이스를 타입스크립트로 작성했다고 한다. Vue3은 컴포지션 API와 함께 타입스크립트를 사용하기 위해서 컴포지션 API 내부의 setup() 함수에서 자동으로 타입을 추론하기 때문에 사용하기 훨씬 수월해졌다.

Vue3의 핵심 Composition API

기존의 vue에서도 컴포넌트를 생성하고 재사용이 가능했었다. 하지만, 위의 문제처럼 대규모 프로젝트에서 수 백, 수 천개의 컴포넌트를 생각하면 충분하지 않았다. 이렇듯 대규모 어플리케이션에서는 코드의 재사용성이 정말 정말 정말 중요하다! 우선 Composition API 작업을 시작하려면, 실제로 사용할 수 있는 장소가 필요하다. 그게 Vue 컴포넌트에서는 이 위치를 setup이라고 부른다.

setup 컴포넌트 옵션

새로운 setup 컴포넌트 옵션은 컴포넌트가 생성되기 전에, props가 한번 resolved될 때 실행된다. 그리고 composition API의 진입점 역할을 한다. setup이 실행될 때, 컴포넌트 인스턴스가 아직 생성되지않아 setup 옵션 내에 this가 존재하지 않는다. 즉, props를 제외하고, 컴포넌트 내 다른 속성에 접근할 수 없다.( local state, computed properties 또는 methods.)

반응형 변수 ref

Vue 3.0에서는 다음과 같이 ref 펑션을 사용하여 어디서나 변수를 반응성있도록 생성할 수 있다.

import { ref } from 'vue'

const counter = ref(0)

ref는 전달인자를 받고 반응형 변수의 값에 접근하거나 값을 변경할 수 있는 value 속성을 가진 객체를 반환한다.

import { ref } from 'vue'

const counter = ref(0)

console.log(counter) // { value: 0 }
console.log(counter.value) // 0

counter.value++
console.log(counter.value) // 1

객체 안에 값을 감싸는 것은 불필요할 수 있지만, JavaScript 에서 다른 데이터 타입에 걸쳐 동작을 통일시켜야 한다. JavaScript에서는 NumberString 과 같은 원시 타입(primitive types)은 참조에 의한 전달(pass by reference)이 아니라 값에 의한 전달(pass by value)이기 때문이다.

모든 값을 감싸는 래퍼 객체(wrapper object)를 가지고 있으면 어딘가에서 반응성을 잃어버릴 염려없이 전체 앱에서 안전하게 전달할 수 있다.

정리해서, ref 펑션은 값에 반응형 참조를 만든다.

setup안에 라이프사이클 훅 등록하기

기존의 option API와 비교하여 Composition API를 완벽하게 만들기 위해서는 setup안에 라이프사이클 훅을 등록하는 방법이 필요하다. 기존의 훅과 동일한 기능을 갖기만 접두사 on이 붙는다.

mounted => onMounted

해당 라이프사이클 훅은 컴포넌트에 의해 훅이 호출될 때 실행될 콜백을 받는다.

vue2의 mounted와 동일하게 인스턴트가 마운트된 후에 호출되는 훅이다. 달리 말해 가상 DOM이 실제 DOM에 부착되고 난 이후에 실행되는 펑션이다.

// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted } from 'vue'

// 컴포넌트 내부
setup (props) {
  const repositories = ref([])
  const getUserRepositories = async () => {
    repositories.value = await fetchUserRepositories(props.user)
  }

  onMounted(getUserRepositories) // `mounted`에서 `getUserRepositories` 호출

  return {
    repositories,
    getUserRepositories
  }
}

해당 컴포넌트가 실행될 때 onMounted 훅을 호출한다.


Composition API의 더 쉬운 표현, <script setup>

SFC (single file Component, 싱글 파일 컴포넌트)는 한 파일이 하나의 요소를 정의하는 방식의 컴포넌트이다. 일반적으로 파일에 HTML, 스타일 태그 및 스크립트 태그가 모두 들어있는 파일과 유사하다. SFC와 Composition API를 모두 사용하는 경우 권장되는 구문이다. 일반적인 <script> 보다 많은 이점을 제공한다.

  • 더 적은 보일러플레이트로 간결한 코드

    보일러플레이트(boilerplate)란 변경 없이 계속해서 재사용할 수 있는 코드를 말한다.

  • 순수 타입스크립트를 사용하여 props and emitted event 선언 가능
  • 더 나은 런타임 성능

    템플릿이 중간 프록시 없이 동일한 scope의 렌더링 함수로 컴파일 됨.

  • 더 나은 IDE 유형 추론 가능

기본적인 구성요소는 다음과 같다.

<template>
  <div> hello world</div>
</template>

<script lang="ts">
  console.log('여기는 타입스크립트를 작성하는 스크립트')
</script>

<script lang="setup">
  console.log('스크립트 setup')
</script>

setup으로 선언된 script안에는 컴포넌트 안 setup() 함수로 컴파일 된다. 즉, 컴포넌트를 처음 가져올 때 한 번만 실행되는 setup() 함수와 달리 <script setup> 안 코드는 컴포넌트가 실행될 때마다 해당 코드가 실행된다.

최상위 바인딩은 템플릿에서 직접 사용 가능하다.

<template>
  <div @click="onClickEvent"> </div>
  <div> </div>
</template>

<script lan="setup">
  import { anOterFuntion } from '~/util';

  const name = 'seongsik';

  const onClickEvent () => {
    console.log(name);
  }
</script>
  • <script setup>안 최상위 바인딩은 템플릿에서 정의된 이름 그대로 사용할 수 있다.

이처럼 SFC하나로 모두 정의(impert, composition API, 타입스크립트, CSS) 할 수 있는 것이 가장 큰 장점인 것 같다. 다음에는 SFC의 setup 구문을 더 자세히 다뤄보도록 하자