[Vue.js] Vue 3

강경서·2023년 9월 18일
0
post-thumbnail

Vue

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

Vue의 두 가지 핵심 기능

  • 선언적 렌더링(Declarative Rendering): Vue는 표준 HTML을 템플릿 문법으로 확장하여 JavaScript 상태(State)를 기반으로 화면에 출력될 HTML을 선언적(declaratively)으로 작성할 수 있습니다.
  • 반응성(Reactivity): Vue는 JavaScript 상태(State) 변경을 추적하고, 변경이 발생하면 DOM을 효율적으로 업데이트하는 것을 자동으로 수행합니다.

프로그레시브 프레임워크

Vue는 프론트엔드 개발에 필요한 대부분의 공통 기능을 다루는 프레임워크이자 생태계입니다. 그러나 웹은 매우 다양해 구축하려는 것의 형태와 규모가 크게 다를 수 있습니다. 이를 염두에 두고 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)을 하나의 파일에 캡슐화합니다. 이전에 보았던 예제는 다음과 같이 SFC 형식으로 작성할 수 있습니다.

<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>

<template>
  <button @click="count++">Count is: {{ count }}</button>
</template>

<style scoped>
button {
  font-weight: bold;
}
</style>

API 스타일

Vue 컴포넌트는 옵션(Options) API와 컴포지션(Composition) API 두 가지 스타일로 작성할 수 있습니다.


옵션 API

옵션 API를 사용하여 옵션의 data, methods 및 mounted 같은 객체를 사용하여 컴포넌트의 로직를 정의합니다. 옵션으로 정의된 속성은 컴포넌트 인스턴스를 가리키는 함수 내부의 this에 노출됩니다.

<script>
export default {
  // data()에서 반환된 속성들은 반응적인 상태가 되어 `this`에 노출됩니다.
  data() {
    return {
      count: 0
    }
  },

  // methods는 속성 값을 변경하고 업데이트 할 수 있는 함수.
  // 템플릿 내에서 이벤트 헨들러로 바인딩 될 수 있음.
  methods: {
    increment() {
      this.count++
    }
  },

  // 생명주기 훅(Lifecycle hooks)은 컴포넌트 생명주기의 여러 단계에서 호출됩니다.
  // 이 함수는 컴포넌트가 마운트 된 후 호출됩니다.
  mounted() {
    console.log(`숫자 세기의 초기값은 ${ this.count } 입니다.`)
  }
}
</script>

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

컴포지션 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>

Vue 튜토리얼

컴포지션 API와 SFC(싱글 파일 컴포넌트)를 사용하여 진행한 Vue 튜토리얼입니다.


선언적 렌더링

Vue의 핵심 기능은 선언적 렌더링입니다. HTML을 확장하는 템플릿 문법을 사용하여 JavaScript 상태를 기반으로 HTML이 어떻게 보이는지 설명할 수 있습니다. 상태가 변경되면 HTML이 자동으로 업데이트됩니다.

변경 시, 업데이트를 트리거할 수 있는 상태는 반응형으로 간주됩니다. Vue의 reactive() API를 사용하여 반응형 상태를 선언할 수 있습니다.

import { reactive } from 'vue'

const counter = reactive({
  count: 0
})

console.log(counter.count) // 0
counter.count++

reactive()는 객체(배열, Map, Set과 같은 빌트인 타입 포함)에서만 작동합니다. 반면에 ref()는 모든 타입의 값을 사용할 수 있으며, .value 속성으로 내부 값을 노출하는 객체를 생성합니다.

import { ref } from 'vue'

const message = ref('안녕 Vue!')

console.log(message.value) // "안녕 Vue!"
message.value = '메세지 변경됨'

컴포넌트의 <script setup> 블록에 선언된 반응형 상태는 템플릿에서 직접 사용할 수 있습니다. 이것은 이중 중괄호 문법을 사용하여 counter 객체와 message ref의 값을 동적으로 텍스트로 렌더링하는 방법입니다.

<h1>{{ message }}</h1>
<p>숫자 세기: {{ counter.count }}</p>

템플릿에서 message ref에 접근할 때, .value를 사용할 필요가 없습니다! 보다 간결한 사용을 위해 자동으로 언래핑됩니다.

이중 중괄호 내부의 내용은 식별자나 경로에만 국한되지 않습니다. 유효한 JavaScript 표현식을 사용할 수도 있습니다.

<h1>{{ message.split('').reverse().join('') }}</h1>

속성 바인딩

Vue에서 이중 중괄호는 텍스트 삽입에만 사용됩니다. 속성을 동적 값에 바인딩하려면 v-bind 디렉티브를 사용합니다.

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

디렉티브는 v- 접두사로 시작하는 특수한 속성으로 Vue 템플릿 문법의 일부입니다. 텍스트 삽입과 유사하게 디렉티브 값은 컴포넌트의 상태에 접근할 수 있는 JavaScript 표현식입니다.
템플릿 문법 자세히 보기

콜론(:) 뒤의 부분(id)은 디렉티브의 "인자"입니다. 여기서 엘리먼트의 id 속성은 컴포넌트 상태의 dynamicId 속성과 동기화됩니다.

v-bind는 너무 자주 사용되기 때문에 전용 단축 문법이 있습니다.

<div :id="dynamicId"></div>

이벤트 리스너

v-on 디렉티브를 사용하여 DOM 이벤트를 수신할 수 있습니다.

<button v-on:click="increment">{{ count }}</button>

자주 사용되기 때문에 v-on에는 다음과 같은 단축 문법도 있습니다.

<button @click="increment">{{ count }}</button>

여기서 참조되는 incremen<script setup>에서 선언된 함수입니다.

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

const count = ref(0)

function increment() {
  // 컴포넌트의 count 상태 업데이트
  count.value++
}
</script>

함수 내에서 ref 값을 변경하여 컴포넌트 상태를 업데이트할 수 있습니다.

이벤트 핸들러는 인라인 표현식을 사용할 수도 있으며, 수식어를 사용하여 일반적인 작업을 단순화할 수 있습니다.
이벤트 핸들링 자세히 보기


폼(form) 바인딩

v-bindv-on을 함께 사용하면, 폼 안의 입력 엘리먼트에 양방향 바인딩을 만들 수 있습니다.

<input :value="text" @input="onInput">
function onInput(e) {
  // v-on 핸들러는 네이티브 DOM 이벤트를 인자로 받습니다.
  text.value = e.target.value
}

Vue는 양방향 바인딩을 단순화하기 위해, 위 문법을 간편 표기하는 v-model 디렉티브를 제공합니다.

<input v-model="text">

-model<input>의 값을 바인딩된 상태와 자동으로 동기화하므로, 더 이상 이에 대한 이벤트 핸들러를 사용할 필요가 없습니다.

v-model은 텍스트 입력 외에도 체크박스, 라디오 버튼, 셀렉트 드롭다운과 같은 다른 입력 타입에서도 작동합니다.
Form 입력 바인딩 자세히 보기


조건부 렌더링

엘리먼트를 조건부로 렌더링하기 위해 v-if 디렉티브를 사용할 수 있습니다.

<h1 v-if="awesome">Vue는 굉장해! 엄청나!</h1>

<h1>awesome의 값이 truthy인 경우에만 렌더링됩니다. awesomefalsy 값으로 변경되면 DOM에서 제거됩니다.

또한 v-elsev-else-if를 사용하여 조건의 다른 분기를 나타낼 수도 있습니다.

<h1 v-if="awesome">Vue는 굉장해! 엄청나!</h1>
<h1 v-else>오 안돼 😢</h1>

리스트 렌더링

v-for 디렉티브를 사용하여 자료 배열을 엘리먼트 목록으로 렌더링할 수 있습니다.

<ul>
  <li v-for="todo in todos" :key="todo.id">
    {{ todo.text }}
  </li>
</ul>

여기서 odo는 현재 반복 중인 배열 앨리먼트를 나타내는 로컬 변수입니다. 함수 범위와 유사하게v-for 앨리먼트 위 또는 내부에서만 액세스할 수 있습니다.

각 todo 객체에 고유한 id를 부여하고, 각 <li>에 특별한 속성인 key를 바인딩했습니다. key를 사용하면 Vue가 각 <li>를 정확하게 이동시켜 배열에서 해당 객체의 위치와 일치하도록 할 수 있습니다.

목록을 업데이트하는 방법에는 두 가지가 있습니다.

  • 자료 배열에서 변경 메서드(mutating methods)를 호출합니다.
todos.value.push(newTodo)
  • 배열을 새 배열로 교체합니다.
todos.value = todos.value.filter(/* ... */)

계산된 속성

이전 단계의 할 일 목록을 계속 만들어 나가봅시다. 여기에 이미 각 할 일에 토글 기능을 추가했습니다. 이것은 각 할 일 객체에 done 속성을 추가하고,v-model을 사용하여 체크박스에 바인딩함으로써 작동합니다.

<li v-for="todo in todos">
  <input type="checkbox" v-model="todo.done">
  ...
</li>

computed()를 사용하여, 반응 데이터 소스를 기반으로 .value를 계산하는 계산된 참조(ref)를 만들 수 있습니다:

import { ref, computed } from 'vue'

const hideCompleted = ref(false)
const todos = ref([
  /* ... */
])

const filteredTodos = computed(() => {
  // `todos.value` 및 `hideCompleted.value`를
  // 기반으로 필터링된 할 일을 반환.
})
- <li v-for="todo in todos">
+ <li v-for="todo in filteredTodos">

계산된 속성은 계산에 사용된 다른 반응형 상태를 의존성으로 추적합니다. 결과를 캐시하고 의존성이 변경되면 자동으로 업데이트합니다.


생명주기와 템플릿 참조

지금까지 Vue는 반응형 및 선언적 렌더링으로 모든 DOM 업데이트를 처리해 왔습니다. 그러나 필연적으로 DOM을 수동으로 작업해야 하는 경우가 있습니다.

우리는 특별한 속성인 ref를 사용하여 템플릿 참조를 요청할 수 있습니다.

<p ref="pElementRef">안녕</p>

참조에 접근하려면 다음과 같이 이름이 일치하는 ref를 선언해야 합니다.

const pElementRef = ref(null)

ref는 null 값으로 초기화합니다. <script setup> 실행 시 해당 엘리먼트가 아직 존재하지 않기 때문입니다. 템플릿 참조는 컴포넌트가 마운트된 후에만 접근할 수 있습니다.

마운트된 후, 코드를 실행하려면 onMounted() 함수를 사용해야 합니다.

import { onMounted } from 'vue'

onMounted(() => {
  // 이제 컴포넌트가 마운트되었습니다.
})

이것을 컴포넌트 생명 주기 훅이라고 하며, 생명 주기의 특정 시간에 호출할 콜백을 등록할 수 있습니다. onUpdatedonUnmounted와 같은 다른 훅도 있습니다.
생명 주기 도표 자세히 보기


감시자

때때로 우리는 반응형 "사이드 이펙트"를 수행해야 합니다. 예를 들어 숫자가 변경될 때마다, 콘솔 로그로 출력하는 것입니다. 우리는 watch(감시자)로 이를 구현할 수 있습니다.

import { ref, watch } from 'vue'

const count = ref(0)

watch(count, (newCount) => {
  // 예, console.log()는 사이드 이펙트입니다.
  console.log(`새로 센 숫자 값은: ${newCount}`)
})

watch()ref를 직접 감시할 수 있으므로, count의 값이 변경될 때마다 콜백이 실행됩니다. watch()는 다른 타입의 데이터 소스도 볼 수 있습니다.
감시자 자세히 보기


컴포넌트

지금까지 우리는 단일 컴포넌트로만 작업했습니다. 일반적으로 실제 Vue 앱은 중첩된 컴포넌트를 사용하여 생성됩니다.

상위 컴포넌트는 다른 컴포넌트를 템플릿의 하위 컴포넌트로 렌더링할 수 있습니다. 자식 컴포넌트를 사용하려면 먼저 가져와야 합니다.

import ChildComp from './ChildComp.vue'

그런 다음 템플릿에서 컴포넌트를 다음과 같이 사용할 수 있습니다.

<ChildComp />

Props

자식 컴포넌트는 props를 통해 부모로부터 데이터를 받을 수 있습니다. 우선, 허용할 props를 선언해야 합니다.

<!-- ChildComp.vue -->
<script setup>
const props = defineProps({
  msg: String
})
</script>

참고로 defineProps()는 컴파일 타임 매크로이므로 import 할 필요가 없습니다. 일단 선언되면 msg prop은 자식 컴포넌트 템플릿에서 사용할 수 있습니다. 또한 defineProps()에서 반환된 객체는 JavaScript에서 접근할 수 있습니다.

부모는 속성을 사용하는 것처럼 자식에게 prop을 전달할 수 있습니다. 동적 값을 전달하기 위해 v-bind 문법을 사용할 수도 있습니다.

<ChildComp :msg="greeting" />

Emits

자식 컴포넌트는 부모로부터 props를 받는 것 뿐만 아니라 이벤트를 emit(발송)할 수도 있습니다.

<script setup>
// emit할 이벤트 선언
const emit = defineEmits(['response'])

// 인자와 함께 emit
emit('response', '자식 컴포넌트로부터 🌷를 받았어요!')
</script>

emit()의 첫 번째 인자는 이벤트 이름입니다. 이후 추가되는 모든 인자는 이벤트 리스너에 전달됩니다.

부모는 v-on을 사용하여 자식이 발송한 이벤트를 수신할 수 있습니다. 아래 예제 코드는 자식이 이벤트를 발송할 때 추가한 인자를 핸들러에서 받아 로컬 상태에 할당한 것입니다.

<ChildComp @response="(msg) => childMsg = msg" />

슬롯

부모 컴포넌트는 자식에게 props를 사용하여 데이터를 전달하는 것 외에도, 슬롯을 사용하여 템플릿 조각을 전달할 수 있습니다.

<ChildComp>
  이것은 슬롯 컨텐츠입니다!
</ChildComp>

자식 컴포넌트가<slot> 엘리먼트를 "발산 수단(outlet: 가이드에서 '아울렛'으로 표기됨)"으로 사용하면, 부모에게 전달 받은 슬롯 컨텐츠를 렌더링할 수 있습니다.

<!-- 자식 템플릿에서 -->
<slot/>

<slot> 아울렛 내부 컨텐츠는 "대체" 컨텐츠로 처리될 수 있는데, 부모가 슬롯 컨텐츠를 전달하지 않은 경우에 표시됩니다.

<slot>대체: 부모로부터 컨텐츠를 못 받았어요! 😢</slot>

🧾 Reference

profile
기록하고 배우고 시도하고

0개의 댓글