Vue3 공식 문서 이해하기

uxolrv·2023년 5월 7일
0
post-thumbnail

Vue


사용자 인터페이스를 구축하기 위한 JavaScript 프레임워크

표준 HTML, CSS 및 JavaScript를 기반으로 구축되며, 단순한 것부터 복잡한 것까지 사용자 인터페이스를 효율적으로 개발할 수 있는 컴포넌트 기반 프로그래밍 모델을 제공한다.




Vue의 핵심 기능


선언적 렌더링 (Declarative Rendering)
Vue는 표준 HTML을 템플릿 문법으로 확장하여 JavaScript 상태(state)를 기반으로 화면에 출력될 HTML을 선언적(declaratively)으로 작성할 수 있다.


반응성 (Reactivity)
Vue는 JavaScript 상태(state) 변경을 추척하고, 변경이 발생하면 DOM을 효율적으로 업데이트하는 것을 자동으로 수행한다.




프로그레시브 프레임워크


Vue는 프론트엔드 개발에 필요한 대부분의 공통 기능을 다루는 프레임워크이자 생태계다.

사용 사례에 따라 Vue를 다양한 방식으로 사용할 수 있다.

  • 빌드 과정 없이 정적 HTML에 적용
  • 모든 페이지에 웹 컴포넌트로 추가
  • 싱글 페이지 어플리케이션 (SPA: Single-Page Application)
  • Fullstack / 서버 사이드 렌더링 (SSR: Server-Side-Rendering)
  • Jamstack / 정적 사이트 생성 (SSG: Static-Site-Generation)
  • 데스크톱, 모바일, WebGL 또는 터미널을 대상으로 하는 경우




싱글 파일 컴포넌트


빌드 도구를 사용하는 대부분의 Vue 프로젝트에서는 HTML과 유사한 싱글 파일 컴포넌트(Single-File Component: SFC, *.vue 파일)라는 파일 형식을 사용하여 Vue 컴포넌트를 작성한다.

Vue SFC는 컴포넌트의 논리(JavaScript), 템플릿(HTML), 스타일(CSS)을 하나의 파일에 캡슐화한다.




API 스타일


옵션(Options) API컴포지션(Composition) API 두 가지 스타일로 작성할 수 있다.


옵션 API

옵션 API를 사용하여 옵션의 data, methodsmounted 같은 객체를 사용하여 컴포넌트의 로직을 정의한다.

옵션으로 정의된 속성은 컴포넌트 인스턴스를 가리키는 함수 내부의 this에 노출된다.


<script>
  export default {
	data() {
      return {
        count: 0
      }
    },
    
    methods: {
      increment() {
        this.count++
      }
    },
    
    mounted() {
      console.log(`숫자 세기의 초기값은 ${ this.count } 입니다.`)
    }
  }
</script>

<template>
  <button @click="increment">숫자 세기: {{ count }}</button>
</template>

옵션 API는 일반적으로 OOP 언어 배경을 가진 사용자를 위한 클래스 기반 모델과 더 잘 맞는 "컴포넌트 인스턴스"(this)의 개념을 중심으로 한다.

또한 반응형 세부 사항을 추상화하고 옵션 그룹을 통해 코드 구조를 실행하여 초보자에게 더 친숙하다.




컴포지션 API

컴포지션 API를 사용하는 경우, import해서 가져온 API 함수들을 사용하여 컴포넌트의 로직을 정의한다.


SFC에서 컴포지션 API는 일반적으로 <script setup>과 함께 사용된다.

setup 속성은 Vue가 더 적은 코드 문맥으로 컴포지션 API를 사용하고, 컴파일 시 의도한대로 올바르게 동작할 수 있게 코드를 변환하도록 하는 힌트다.

예를 들어 <script setup>import되어 가져온 객체들과 선언된 최상위 변수 및 함수는 템플릿에서 직접 사용할 수 있다.


<script setup>
import { ref, onMounted } from 'vue'

const count = ref(0)

function increment() {
  count.value++
}

onMounted(() => {
  console.log(`숫자 세기의 초기값은 ${ count.value } 입니다.`)
})
</script>

<template>
  <button @click="increment">숫자 세기: {{ count }}</button>
</template>

컴포지션 API는 함수 범위에서 직접 반응형 변수를 선언하고 복잡성을 처리하기 위해 여러 함수의 상태를 함께 구성하는데 중점을 둔다.

⇒ 보다 자유로운 형식이며 Vue에서 반응형이 효과적으로 사용되는 방식에 대한 이해가 필요하다.

⇒ 대신 로직을 구성하고 재사용하기 위한 보다 강력한 패턴을 가능하게 한다.






Vue 앱 만들기


모든 Vue 앱은 createApp 함수를 사용하여 새로운 앱 인스턴스를 생성하는 것으로 시작한다.

import { createApp } from 'vue'

const app = createApp({
  /* 최상위 컴포넌트 옵션 */
})




최상위 컴포넌트


createApp에 전달하는 객체는 사실 컴포넌트다.
모든 앱에는 다른 컴포넌트를 자식으로 포함할 수 있는 "최상위 컴포넌트"가 필요하다.

싱글 파일 컴포넌트를 사용하는 경우 일반적으로 다른 파일에서 루트 컴포넌트를 가져온다.

import { createApp } from 'vue'

import App from './App.vue'

const app = createApp(App)

대부분의 실제 앱은 중첩되고 재사용 가능한 컴포넌트 트리로 구성된다.




앱 마운트하기


앱 인스턴스는 .mount() 메서드가 호출될 때까지 아무것도 렌더링하지 않는다.


<div id="app"></div>
app.mount('#app')

앱의 최상위 컴포넌트 컨텐츠는 컨테이너 엘리먼트 내에서 렌더링된다.


import { createApp } from 'vue'
import App from './App.vue'

createApp(App).mount('#app')

.mount() 메서드는 반드시 앱의 환경설정 및 에셋(asset)이 모두 등록 완료된 후에 호출되어야 한다.
⇒ Vue 인스턴스가 사용할 데이터나 컴포넌트 등을 사전에 정의하고 등록해야 한다.

createApp()을 통해 컴포넌트를 등록하고 mount()를 호출해야 한다.


Vue.component('컴포넌트 이름', 컴포넌트 옵션);

또한 에셋 등록 메서드와 달리 반환 값은 앱 인스턴스가 아닌 최상위 컴포넌트 인스턴스다.
.component()를 통해 전역적으로 사용할 컴포넌트(asset)을 등록할 수 있다.




DOM 내부의 최상위 컴포넌트 템플릿

빌드 과정 없이 Vue를 사용할 때, 마운트 컨테이너 내부에 직접 최상위 컴포넌트의 템플릿을 작성할 수 있다.

<div id="app">
  <button @click="count++">{{ count }}</button>
</div>
import { createApp } from 'vue'

const app = createApp({
  data() {
    return {
      count: 0
    }
  }
})

app.mount('#app')

최상위 컴포넌트에 template 옵션이 없으면 Vue는 자동으로 컨테이너의 innerHTML을 템플릿으로 사용한다.




앱 환경설정


앱 인스턴스는 몇 가지 앱 레벨의 옵션을 구성할 수 있는 .config 객체를 노출한다.
예를 들어 모든 자식 컴포넌트에서 에러를 캡처하는 앱 레벨의 에러 핸들러를 정의한다.

app.config.errorHandler = (err) => {
  /* 에러 처리 */
}

앱 인스턴스는 앱 범위의 에셋을 등록하기 위한 몇 가지 방법도 제공한다.

app.component('TodoDeleteButton', TodoDeleteButton)

이렇게 하면 TodoDeleteButton을 앱 어디에서나 사용할 수 있다.




멀티 앱 인스턴스


앱 인스턴스는 동일한 페이지 내 하나로 제한되지 않는다.
createApp API를 사용하면 여러 Vue 앱이 동일한 페이지에 공존할 수 있으며, 각각은 구성 및 전역 자산에 대한 고유한 범위를 갖는다.

const app1 = createApp({
  ...
})

app1.mount('#container-1')

const app2 = createApp({
  ...
})

app2.mount('#container-2')

서버에서 렌더링 된 대규모 페이지의 HTML을 개선하고 특정 부분을 제어하기 위해 Vue가 필요한 경우, 페이지 전체를 단일 Vue 앱 인스턴스로 마운트하지 않아야 한다.
⇒ 대신 여러 개의 작은 앱 인스턴스를 만들고 담당하는 엘리먼트에 마운트한다.






템플릿 문법


Vue는 컴포넌트 인스턴스의 데이터를 서술적으로 렌더링된 DOM에 바인딩할 수 있는 HTML 기반 템플릿 문법을 사용한다.

모든 Vue 템플릿은 사양을 준수하는 브라우저 및 HTML 파서에 문법을 분석할 수 있는 문법적으로 유효한 HTML이다.

내부적으로 Vue는 템플릿을 고도로 최적화된 JavaScript 코드로 컴파일한다.
반응형 시스템과 결합된 Vue는 앱 상태의 변경 사항을 적용할 때, 다시 렌더링할 필요가 있는 최소한의 컴포넌트를 지능적으로 파악하고 DOM 조작을 최소화한다.




텍스트 보간법


데이터 바인딩의 가장 기본적인 형태는 "Mustache"(이중 중괄호) 문법을 사용한 텍스트 보간법이다.

<span>메세지: {{ msg }}</span>

이중 중괄호 태그 내 msg는 해당 컴포넌트 인스턴스의 msg 속성의 값으로 대체된다.
또한 msg 속성이 변경될 때마다 업데이트된다.




HTML 출력


이중 중괄호는 데이터를 HTML이 아닌 일반 텍스트로 해석한다.
실제 HTML을 출력하려면 v-html 디렉티브를 사용해야 한다.

디렉티브는 Vue에서 제공하는 특수한 속성임을 나타내기 위해 접두사 v-를 사용하며, 렌더링된 DOM에 특별한 반응적 동작을 적용한다.



<template>
  <p>텍스트 보간법 사용: {{ rawHtml }}</p>
  <p>v-html 디렉티브 사용: <span v-html="rawHtml"></span></p>
</template>

<script>
export default {
  data() {
    return {
      rawHtml: '<span style="color: red">이것은 빨간색이어야 합니다.</span>',
    };
  },
};
</script>

span 내부의 컨텐츠는 rawHtml 속성 값을 일반 HTML로 해석한 것으로 대체된다.
⇒ 데이터 바인딩은 무시된다.


Vue는 문자열 기반 템플릿 엔진이 아니기 때문에 v-html을 사용하여 템플릿의 일부분을 작성할 수 없다.
⇒ 따라서 UI 재사용 및 구성을 위한 기본 단위로 컴포넌트가 선호된다.


❗️ 보안 경고


웹사이트에서 임의의 HTML을 동적으로 렌더링하면 XSS 취약점이 쉽게 발생할 수 있으므로 매우 위험하다.
따라서 신뢰할 수 있는 컨텐츠에만 v-html을 사용하고 사용자가 제공한 컨텐츠에는 절대 사용하지 않아야 한다.




속성 바인딩


이중 중괄호는 HTML 속성 내에서 사용할 수 없다.
⇒ 대신 v-bind 디렉티브를 사용한다.


<div v-bind:id="dynamicId"></div>

v-bind 디렉티브는 엘리먼트의 id 속성을 컴포넌트의 dynamicId 속성과 동기화된 상태로 유지하도록 Vue에 지시한다.

바인딩된 값이 null 또는 undefined면 엘리먼트의 속성이 제거된 상태로 렌더링된다.




단축 문법

v-bind:로 단축하여 사용할 수 있다.

:는 Vue를 지원하는 모든 브라우저에서 올바르게 파싱할 수 있으며, 최종 렌더링된 마크업에는 표시되지 않는다.




불리언(Boolean) 속성

<button :disabled="isButtonDisabled">버튼</button>

isButtonDisabled에 truthy 값이 있는 경우, disabled 속성이 표기되며 falsy 값의 경우 속성이 생략된다.
값이 빈 문자열인 경우 <button disabled="">와 동일하므로, 속성이 표기된다.




여러 속성을 동적으로 바인딩

data() {
  return {
    objectOfAttrs: {
      id: 'container',
      class: 'wrapper'
    }
  }
}
<div v-bind="objectOfAttrs"></div>

<!--
<div :id="objectOfAttrs.id" :class="objectOfAttrs.class"></div>
와 동일하다
-->

여러 속성을 나타내는 JavaScript 객체가 있는 경우, 인자 없이 v-bind를 사용하여 단일 엘리먼트에 바인딩할 수 있다.




JavaScript 표현식 사용


Vue는 모든 데이터 바인딩 내에서 JavaScript 표현식의 모든 기능을 지원한다.


{{ number + 1 }}

{{ ok ? '예' : '아니오' }}

{{ message.split('').reverse().join('') }}

<div :id="`list-${id}`"></div>

이러한 표현식은 현재 컴포넌트 인스턴스의 데이터 범위에서 JavaScript로 평가된다.


Vue 템플릿에서 JavaScript 표현식은 다음과 같은 위치에 사용할 수 있다.

  • 이중 중괄호(텍스트 보간법) 내부
  • 모든 Vue 디렉티브 속성 내부




하나의 표현식만 가능

각 바인딩에는 하나의 단일 표현식만 포함될 수 있다.
표현식은 값으로 평가할 수 있어야 하는 코드 조각이다. (쉽게 말해 return 뒤에 사용할 수 있는 코드)




함수 호출

바인딩 표현식 내에서 컴포넌트에 노출된 메서드를 호출할 수 있다.


<span :title="toTitleDate(date)">
  {{ formatDate(date) }}
</span>

바인딩 표현식 내부에서 호출되는 함수는 컴포넌트가 업데이트될 때마다 호출되므로, 데이터를 변경 또는 비동기 작업을 트리거하는 등의 부작용이 없어야 한다.




제한된 전역 접근

템플릿 표현식은 샌드박스 처리되어 제한된 전역 리스트에만 접근할 수 있다.

// 접근 가능한 전역 리스트
const GLOBALS_WHITE_LISTED =
  'Infinity,undefined,NaN,isFinite,isNaN,parseFloat,parseInt,decodeURI,' +
  'decodeURIComponent,encodeURI,encodeURIComponent,Math,Number,Date,Array,' +
  'Object,Boolean,String,RegExp,Map,Set,JSON,Intl,BigInt,console'

리스트에 명시적으로 포함되지 않은 window와 같은 전역 속성은 템플릿 표현식에서 접근할 수 없다.
그러나 app.config.globalProperties에 추가하여, Vue 내부의 모든 표현식에서 전역 속성에 접근할 수 있도록 명시적으로 정의할 수 있다.




디렉티브


v- 접두사가 있는 특수한 속성

디렉티브 속성 값은 단일 JavaScript 표현식이어야 한다. (v-for, v-on, v-slot 제외)

디렉티브의 역할은 표현식 값이 변경될 때 DOM에 반응적으로 업데이트를 적용하는 것이다.


<p v-if="seen">이제 이것을 볼 수 있습니다.</p>

여기서 v-if 디렉티브는 seen의 불리언 값을 기반으로 <p> 엘리먼트를 삽입 또는 제거한다.




인자

일부 디렉티브는 디렉티브 뒤에 콜론(:)으로 표시되는 "인자"를 사용할 수 있다.

예를 들어 v-bind 디렉티브는 HTML 속성을 반응적으로 업데이트하는 데 사용된다.


<a v-bind:href="url"> ... </a>

<!-- 단축 문법 -->
<a :href="url"> ... </a>

여기서 hrefv-bind 디렉티브의 인자로서, 엘리먼트의 속성인 hrefurl 값을 바인딩한 것이다.

간단하게 인자 앞의 v-bind::로 줄여 쓸 수 있다.


<a v-on:click="doSomething"> ... </a>

<!-- 단축 문법 -->
<a @click="doSomething"> ... </a>

DOM 이벤트를 수신하는 v-on 디렉티브 또한 단축형으로 @를 대신 사용할 수 있다.
여기서 click의 인자는 수신할 이벤트 이름이다.




동적인 인자

디렉티브의 인자를 대괄호로 감싸서 JavaScript 표현식으로 사용할 수도 있다.


<a v-bind:[attributeName]="url"> ... </a>

<!-- 단축 문법 -->
<a :[attributeName]="url"> ... </a>

여기서 attributeName은 JavaScript 표현식으로 동적으로 평가되며, 평가된 값은 인자의 최종값으로 사용된다.

예를 들어 컴포넌트 인스턴스의 데이터에 attributeName 속성값이 "href"인 경우, 이 바인딩은 v-bind:href와 같다.


<a v-on:[eventName]="doSomething"> ... </a>

<!-- 단축 문법 -->
<a @[eventName]="doSomething">

eventName의 값이 "focus"인 경우, v-on:[eventName]v-on:focus와 같다.



동적인 인자 값 제약 조건

동적인 인자는 null 또는 문자열로 평가되어야 한다.

  • 값이 null일 경우, 바인딩을 명시적을 제거한다.
  • 문자열이 아닌 다른 값은 에러를 트리거한다.



동적인 인자 문법 제약 조건

1️⃣ 동적인 인자 표현식에는 공백 및 따옴표와 같은 특정 문자가 HTML 속성 이름 내에서 유효하지 않기 때문에 문법에 일부 제약 조건이 있다.

<!-- 이렇게 하면 컴파일러 경고가 발생한다. -->
<a :['foo' + bar]="value"> ... </a>

복잡한 동적인 인자를 전달해야 하는 경우 computed 속성을 사용하는 것이 좋다.


2️⃣ DOM 내 템플릿(HTML 파일에 직접 작성된 템플릿)을 사용할 때, 브라우저가 속성 이름을 소문자로 강제 변환하므로 대문자로 키 이름을 지정하는 것도 피해야 한다.

<a :[someAttr]="value"> ... </a>

위 내용은 DOM 내 템플릿에서 :[someattr]로 변환된다.
컴포넌트에 someattr 대신 someAttr 속성을 사용했다면 코드가 작동하지 않는다.
단, 싱글 파일 컴포넌트 내의 템플릿은 이 제약 조건에 해당하지 않는다.




수식어

수식어는 점(.)으로 시작하는 특수한 접미사로, 디렉티브가 특별한 방식으로 바인딩되어야 함을 나타낸다.


<form @submit.prevent="onSubmit">...</form>

예시로 .prevent 수식어는 트리거된 이벤트에서 event.preventDefault()를 호출하도록 v-on 디렉티브에 지시한다.

profile
안녕하세연🙋 프론트엔드 개발자입니다

0개의 댓글