KB_ITs_Your_Life_6th

[Vue.js] Vue 기초

아란정 2025. 3. 20. 20:19

공식문서) https://ko.vuejs.org/


프론트할 건데 React랑 Vue 중에 뭘 배워야 할까? 프론트하면 리액트라고 알고 있긴 했지만, Vue 배운다는데 리액트 바라보며 미련 갖긴 싫어서 Vue를 배울 이유도 찾아보았다.

  1. Vue는 러닝 커브가 타 프레임워크에 비해 완만하다. 배우기가 쉽다.
  2. 협업의 특성상 공유된 형식이 있어야 유지보수가 원활한데 Vue는 형식이 거의 고정되어 있어 협업에 이점이 있다.
  3. JS 풀스택이 된다. Node.js의 npm으로 Vue.js 관련 도구 설치와 의존 라이브러리 관리가 가능하다.
  4. Vue를 배우고 React를 배우면 러닝 허들이 낮아진다. 

결국, 어떤 라이브러리(언어, 프레임워크) 쓰냐보다 어떤 프로젝트를 하고 싶은지? 를 더 중요하게 생각해야 한다. 나중엔 리액트도 하겠지..

참고 : 본 글 중 가장 중립적인 것 같다.

https://brunch.co.kr/@hongjyoun/96


자 이제 Vue를 알아보자

Vue는 가상 DOM에서 변경사항만 렌더링하기 때문에 UI 렌더링이 매우 빠르다는 장점이 있다. 가상 DOM은 브라우저 메모리에서 관리되는 돔에 대한 추상 객체다. 추상?이라는 말은 웹페이지 화면을 직접 수정하지 않고, 메모리 속에서만 가상 형태로 변경 사항을 계산하는 구조를 말한다. 변경된 부분 탐지가 목적이기 때문에 가상 DOM을 이용한다. 

더보기

DOM(Document Object Model) :

현재 클릭된 요소(element)에 대해 스타일을 추가하거나 할 때, event로 불러온 요소가 DOM 요소다. HTML 요소 객체로 JS에서 HTML 값을 가져와 조작하고자 사용한다.

 

공식 사이트가 말하는 Vue

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

 온라인에서 튜토리얼로 코드 쓰면서 배우기 가능하다.

 

MVVM 모델

https://012.vuejs.org/guide/

Model - View - ViewModel을 이해하면 Vue의 전체적인 동작 구조를 이해하는 데에 도움이 된다.

1. 사용자가 View에서 이벤트 발생 → Model 데이터 변경 요청

  • 여기서 이벤트는 @click, v-model 등의 이벤트

2. Proxy가 내부 데이터 변경을 감지 → Watcher가 변경을 인식

3. Watcher Render Function을 호출하여 Virtual DOM 업데이트

4. Vue가 변경된 내용을 실제 DOM에 반영 (최적화된 diffing 수행)

ViewModel

//1
var vm = new Vue({ /* options */ })

//2
var vm = Vue.createApp({
	name: "App",
    data(){
    	return model;
    }
})

//3
let {createApp} = Vue;

모델과 뷰를 동기화하는 객체다.VM은 Vue의 반응형 담당으로 Watcher, Render Funtion의 묶음으로 생각하면 된다.

/ View / DOM 트리가 내부 데이터에 직접 접근할 수 없어서

/ Vue / Proxy를 통해서 내부 데이터(==Model)를 변경하고

/ViewModel / watcher가 이를 알림으로 알려 DOM 트리 업데이트(랜더링)가 일으키도록 한다. 

 

View

vm.$el // The View

<div id="app">
	<h1> {{message}} <h1>
</div>

View에 의해 DOM 요소들이 관리된다. view 인스턴스가 생기면 필요한 데이터를 바인딩할 때까지 root를 찾아 자식 노드를 재귀적으로 타고 타고 간다. view가 컴파일되면 데이터 변화에 대해 반응형이 된다. 데이터가 바뀌면 내가 직접 DOM에 접근하지 않아도 view 업데이트는 정밀하게 세분화되어 자동적으로 DOM(HTML 객체)의 텍스트 노드까지 조작할 수 있다. View instance(#app 내부 공간)는 데이터 객체의 속성을 감싸서 직접 접근(proxy)한다. 또한 batch 단위로 비동기적 실행하여 성능을 향상했다. 

=> view는 DOM요소(HTML)에 직접 연결되는 객체

Model

vm.$data // The Model

var model = {
	message:"Hello Vue3"
};

모델은 proxy 내부 데이터다. 예를 들면 비즈니스 로직이나 앱의 실제 데이터가 있다. 이 값은 proxy 내부에 있기 때문에 값이 변경되어도 watcher가 모른다. 이에 따라 Render function도 작동하지 않아 UI가 랜더링되지 않는다. 무슨 말이냐면 vue가 사용자 값에 반응하여 새로운 값이 담긴 UI를 랜더링하기 위해선 ViewModel의 역할이 필수적이라는 것이다. Model의 객체를 ViewModel의 data로 가지면, {vm.객체} 변경 값에는 UI가 변경되지만 {model.객체} 자체의 변경 값은 즉시 반응하여 랜더링되지않고, {vm 값}이 변경되어야 watcher -> render function 을 통해 함께 렌더링된다.


 

v- :속성 바인딩

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

  • 디렉티브: v- 접두사로 시작하는 특수한 속성으로 Vue 템플릿 문법의 일부 : 컴포넌트의 상태에 접근할 수 있는 JavaScript 표현식

 

@이벤트 리스너

v-on 디렉티브를 사용하여 DOM 이벤트를 수신할 수 있다: v-model로 단축 표기(양방향 바인딩시)

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

const text = ref('')

function onInput(e) {
  text.value = e.target.value
}
</script>

<template>
  <!--<input :value="text" @input="onInput" placeholder="여기에 입력하기">-->
  <input v-model="text" placeholder='input here'>
  <p>{{ text }}</p>
</template>

* App.vue 문법이다.

target vs. currentTarget 의 차이?

event.target 실제 이벤트가 발생한 요소
event.currentTarget 이벤트 리스너가 바인딩된 요소

 

v-for: 리스트 렌더링

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

function removeTodo(todo) {
  todos.value = todos.value.filter((t) => t !== todo)
}
</script>

<template>
  <form @submit.prevent="addTodo">
    <input v-model="newTodo" required placeholder="new todo">
    <button>할 일 추가</button>
  </form>
  <ul>
    <li v-for="todo in todos" :key="todo.id">
      {{ todo.text }}
      <button @click="removeTodo(todo)">X</button>
    </li>
  </ul>
</template>

여기서 todo는 현재 반복 중인 배열 앨리먼트를 나타내는 로컬 변수다. 함수 범위와 유사하게 v-for 앨리먼트 위 또는 내부에서만 액세스할 수 있다. 각 todo 객체에 고유한 id를 부여하고, 각 li에 특별한 속성인 key를 바인딩했다. 

  • key를 왜 쓰냐면 Vue가 DOM 요소를 추적해 위치를 직접 변경하고자 할때 key를 이용한다.

 

todos.value.filter((todo) => todo !==todo) 를 썼더니 전체 목록이 삭제되었다. todo가 각 li를 가리키면 한 줄만 삭제되어야 한다고 생각했는데 ?? v-for 로 todo는 반복 중인 todos.value 배열의 element가 된다. todos.value. 를 호출하면 배열을 순회하면서 각각의 todo 요소를 나타낸다. todo !== todo는 배열 순회에서 모든 요소가 자기 자신과 비교되는 꼴이다.

!== 가 t가 todo와 같지 않을 때만 true를 반환해야 하므로 특정 todo를 나타내는 t를 filter에서 사용해야 한다.

v-for로 인덱스 번호 자동 할당하기

객체와 리스트의 문법이 약간 차이가 있다. 

<tr v-for="(person, index) in contacts" ...>

<option v-for="(val, key, index) in objects" ...>

 

Computed(): 계산된 속성

반응 데이터 소스를 기반으로 .value를 계산하는 계산된 참조(ref)를 만들 수 있다: 계산된 속성은 계산에 사용된 다른 반응형 상태를 의존성으로 추적한다. 결과를 캐시하고 의존성이 변경되면 자동으로 업데이트한다.

예시를 보면 computed가 이해가 된다. 버튼을 누르거나 하는 반응형 데이터로 즉시 계산값을 HTML에 반영하도록 할 수 있다는 것이다. Hide button 을 누르면 (전체 값 : not done 값) 중 하나를 보여주도록 하는 코드를 봐보자.

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

const hideCompleted = ref(false)

const filteredTodos = computed(()=>{
    return hideCompleted.value
    ? todos.value.filter((t)=> !t.done)
    : todos.value
})

</script>

<template>
  <ul>
    <li v-for="todo in filteredTodos" :key="todo.id">
      <input type="checkbox" v-model="todo.done">
      <span :class="{ done: todo.done }">{{ todo.text }}</span>
      <button @click="removeTodo(todo)">X</button>
    </li>
  </ul>
    <button @click="hideCompleted = !hideCompleted">
    {{ hideCompleted ? 'Show all' : 'Hide completed' }}
    </button>
</template>

버튼을 누르면 v-for = "todo in filteredTodos" 속성에서 계산된 로직에 따라 항목이 숨겨지거나 보여진다. 여기서 Computed가 사용되는 거다. computed 내부에서 사용하는 변수들을 자동으로 의존성을 추적한다.hideCompleted.value와 todos.value는 filteredTodos의 의존성으로 자동 등록되어 Vue가 변경시 마다 필요한 곳을 자동 업데이트해준다.

(template 내부에선 .value가 없어도 ref 로 선언한 변수를 직접 사용할 수 있다)