꺼내먹는지식 준

Vue 기본기 본문

카테고리 없음

Vue 기본기

알 수 없는 사용자 2023. 4. 4. 20:22

Vue 찍먹

 

NodeJS 를 설치하는 이유는 Terminal CLI 에서 npm 을 이용하기 위함이다. 

 

npm은 각종 웹 개발 라이브러리 설치 도우미이다. 

 

App.vue 파일에 메인 화면을 작성하면 된다. 

App.vue  파일을 html 파일로 컴파일해주기에 가능한 것이다. 

컴파일은 main.js가 한다. 

node modules 프로젝트를 위한 모든 라이브러리 

src 실제로 소스 코드 짜는 공간 

public html, 기타 파일 보관 

package.json 라이브러리 버전 기록 

 

 

 

DOM 이란? 

Document Object Model 

즉, 웹 문서(페이지)를 객체화한 개념 

 

문서 객체란? 

<html>이나 <body> 같은 html문서의 태그들을 JavaScript가 이용할 수 있는 객체(object)로 만든 것 

 

좀 더 쉽게 설명하자면

각 언어마다 처리하는 방법이나 규칙, 즉 문법이 있다. JavaScript와 HTML은 서로 다른 언어이므로 각각 다른 '문법'을 사용한다. 이런 차이 때문에 JavaScript는 HTML을 직접적으로 이해하거나 변경할 수 없고, DOM (문서 객체 모델)은 이런 문제를 해결하는 역할을 한다. DOM은 웹 페이지의 HTML을 JavaScript에서 사용할 수 있도록 "객체"라는 형태로 변환합니다.

객체는 JavaScript의 주요 데이터 타입 중 하나로, 속성과 메소드를 가질 수 있다. 속성은 객체의 특성을 나타내고, 메소드는 객체가 수행할 수 있는 동작을 나타낸다.

예를 들어, HTML의 <p> 태그는 DOM을 통해 Paragraph 객체로 변환되고, 이 객체는 텍스트 내용을 변경하는 메소드를 가질 수 있다.

JavaScript는 이 메소드를 사용해 웹 페이지의 텍스트를 동적으로 변경할 수 있다. 이처럼 DOM은 JavaScript가 HTML을 '이해'하고 조작할 수 있게 만드는 통역사 같은 역할을 한다. 즉, '객체'라는 형태로 변환된 HTML 태그 (문서 객체)는 JavaScript의 문법으로 조작이 가능하게 된다. JavaScript는 이 문서 객체를 통해 웹 페이지에 동적인 기능을 추가하거나 사용자의 상호작용에 반응하는 등의 작업을 수행할 수 있다.

 

DOM의 형태 

DOM은 tree 구조의 형태를 가지고 있다. DOM 에 포함 된 <p>를 더 자세히 살펴보자. <p> 태그도 트리형태의 구조를 형성하고 있다. DOM 객체에 접근하고 또 키워드로 필요한 노드에 접근하여 목표한 작업을 할 수 있다. 

 

 

JavaScript로 문서객체를 생성한다는 것은 어떤 의미인가?

 

문서 객체는 두 가지 방법으로 생성된다. 

 

웹 브라우저가 HTML 페이지에 적혀 있는 태그를 읽으면 생성 된다. 

이런 과정을 정적으로 문서 객체를 생성한다고 말한다. 

단순히 적혀져 있는 그대로 문서객체가 생성 되는 것을 표현한다. 

 

 

 

 

Object Model이 필요한 이유 

스크립트로 사람이 document(화면 요소)를 조작하기 위해서 웹 문서가 스크립트의 객체 형식으로 표현되어야 했다. 

 

Javascript 뿐만 아니라 python이나 다른 스크립트 언어로도 DOM을 조작할 수 있다고 한다. 

Web API 의 사용 예시를 보면 아래와 같다. 

DOM의 노드들 중에서 특정 노드를 선택하고, 선택한 노드의 색을 변경하거나, viewport의 높이를 찾는다거나, DOM을 조작하는 데 사용되는 스크립트 메서드들이 저 공식에 부합하는 web API 라고 불리게 된다. 

 

 

 

 

데이터 바인딩 

 

JS 데이터를 HTML에 꽂아넣는 것이 가능해진다. 

자바스크립트가 변수에 저장을 한다면, vue는 데이터 보관 통이 존재한다. 

 

data() 안에 모든 데이터를 포함해야한다. 

 

 

data() 안에 object 형식으로 데이터를 저장하고, html 안에 JS 데이터를 꽂아 넣었다. 

 

데이터바인딩 왜 할까? 

 

1. 

HTML에 하드코딩으로 그냥 값을 꽂아 넣으면 수정이 어렵다. 

그러나 JS 데이터로 꽂아 넣으면 가변적일 때 쉽게 변경할 수 있기 때문이다. 

 

2.

Vue의 실시간 자동 렌더링 기능을 사용하려면 데이터바인딩을 해야만 한다. 

Vue는 data()를 변경하면 데이터와 관련된 HTML 에도 실시간으로 반영된다. 

이에 따라 웹앱 같은 사이트를 만들 수 있다. (쇼핑몰에서 가격 필터를 사용하거나 할 때 HTML 이 자동으로 변경하게 하기 위하여)

 

당연한 말이지만, 실시간 변할 필요가 없는 데이터들은 데이터바인딩을 할 필요가 없다. 그냥 하드코딩하면 된다. 

 

java script 의 array 

 

var 어레이 = [10, 20, 30]; 

 

를 vue 에서 

어레이 : [10,20,30], 으로 작성 가능하다. 

 

데이터 바인딩은 아래와 같이 표현식도 사용 가능하다. 

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

 

이중 중괄호를 사용한 데이터 바인딩은 텍스트 삽입에만 사용된다. 

속성을 동적 값에 바인딩 하려면 v-bind 디렉티브를 사용한다. 이를 HTML 속성 안에서 데이터 바인딩이라고 이해해도 된다.

워낙 자주 사용 되므로 v-bind: 를 : 로 축약해서 사용한다.  

 

 

반복문 

 

HTML 의 반복적 요소를 vue 의 반복문으로 축약해서 작성 가능하다.

 

우선 HTML <template> 즉, 전반적인 template 을 만들고 CSS <style> template에 style을 더하고, 두가지로 해결 안되는 기능 및 추가적 요소들을 <script > 를 통해 해결한다는 것을 기억하자. 

 

ChatGPT 의 응답에 따르면 아래와 같이 좀 더 직관적으로 정리해준다. 즉, 상호보완적 관계라고 이해하면 된다. 

 

 

 

다음을 반복문으로 좀 더 간단하게 작성해보자. 

 

간단하게 완료 되었다. 

    메뉴들: ['Home', 'Shop', 'About'],

 

for 문 안에서는 어차피 string 이 들어갈 수 없으므로 따로 데이터 바인딩이 필요가 없으나, i 는 데이터 바인딩이 없으면 char 형인지 변수인지 알 수 없으므로 데이터 바인딩이 들어간다. 

 

key의 용도는 반복문을 돌린 요소를 컴퓨터가 구분하기 위해 쓴다. 

 

이는 파이썬의 enumerate 와 같은 용도이다. 

    <a v-for="(a,i) in 메뉴들" :key="i"> {{i}} </a>

 

HTML 속성 안에서 데이터 바인딩은 : 이다.

Event Handler 

  <button>허위매물신고</button> <span>신고수 : 0</span>

다음과 같은 버튼이 있다고 가정해보자. 

 

※ span

 

기존에 javascript는 button 을 사용하기 위해 아래와 같은 형태로 사용했다. 

  <button onclick="">허위매물신고</button> <span>신고수 : 0</span>

 

그러나 Vue 방식은 아래와 같다. 

  <button v-on:click="">허위매물신고</button> <span>신고수 : 0</span>

 

v-on 디렉티브는 이벤트 리스너이다. 

자주 사용되기 때문에 @로 단축해서 사용한다.

 

즉, v-on:click 은 @click 으로 축약해서 쓸 수 있다. 

 

버튼을 누르면 오른쪽 숫자를 증가하도록 설정해보자. 

기존 자바스크립트는 버튼 누르면 숫자 찾아서 +1, 그리고 HTML 에 반영하는 수순이었다. 

 

하지만 Vue는 마치 processing 처럼 실시간 재랜더링이 되기 때문에 신고수만 더해주면 자동으로 html 이 바뀌도록 반영 된다. (Vue의 가장 강력한 특징)

 

<button v-on:click= "신고수++">허위매물신고</button> <span>신고수 : {{신고수}} </span>

 

@mouseover 
@drag 
@... 
등등 다양한 이벤트핸들러 기능이 많다.

 

함수를 쓰는 이유는 긴 코드를 짧고 편리하게 재사용하기 위해서이다. 

 

Vue 에서도 함수가 존재한다. 

간단하게 methods 안에 선언만 해주면 된다. 

유의 사항은 data 의 변수를 참고하는 경우 꼭 this.데이터명 을 선언해줘야 한다.

 

    <button v-on:click= "increase">허위매물신고</button> <span>신고수 : {{신고수}} </span>

 그리고 @click 에는 따로 던져주고 싶은 매개변수가 없을 때 "increase" 특별히 뒤에 () 를 붙이지 않는다는 점, 함수지만 동일하게 " " 를 사용한다는 점 유의하자. 함수가 아니어도 : 뒤에 따라오는 데이터 바인딩은 " " 이다. 

 

당연히 매개변수를 던질 수도 있다.

 

 img 삽입 

    <img src = "./assets/room0.jpg">

 

보통 이미지는 assets 폴더에 저장한다. 

유의사항으로 시작은 무조건 ./ 으로 시작한다. 

 

화면 내에 UI 를 제작할 때 다음만 기억하면 된다. 

 

1. UI는 디자인이 선수적으로 되어 있어야 하고 특정 조건에 따라 나타난다. 

2. UI 상태가 저장되어 있어야 한다. 

3. 데이터에 따라 UI 어떻게 보일지 작성 

 

 

modal_status 가 true 일 때만 팝업 혹은 UI를 보여주자.

 

 

버튼하나를 추가하여 팝업 UI를 닫을 수 있게 했는데, button 도 class 를 지정해서 위치를 바꿔줄 수 있다. 하지만 이게 직관적인 방법인지는 모르겠다. 

 

 

데이터를 받아서 반영 

데이터를 보내고자 하는 java script 파일에서 데이터를 export 한다. 

 

그리고 App.vue 파일의 <script> 안에서 import를 수행한다. 

import 시 export 변수명과 동일하지 않아도 괜찮다. 

 

만약 여러개를 export 하는 것도 가능하고, 

export {apple,apple2}

함수를 export 하는 것도 가능하다. 

 

받을 때는 

import {apple} ~ 

import {apple, apple2} ~

로 받고 싶은 것을 골라 받을 수 있으나, 중괄호를 사용해야한다. 

 

 

 폼 바인딩 

<input value = " ">

input 에 들어가는 초기값을 " "로 설정  

 

<input :value = "text">

input에 들어가는 초기값을 데이터바인딩 된 text 로 설정 

 

<@input = "onInput">

input이 들어오면 onInput 함수 호출 

 

결론적으로 <v-model> 은 함수 호출 없이 input 이 바로 text 에 들어가도록 설정 된 기능 

심지어 : , 즉 이벤트 핸들러도 사용할 필요 없다.  

 

 

조건부 렌더링 

v-if 는 ture일 때 렌더링 되었다면, v-else 는 false일 때 렌더링 된다. 

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

 

아래와 같이 v-else 만으로도 v-if 에서 tracking 하는 상태를 반영하는 듯 하다. 

 

 

함수 (methods)

특별한 유의사항 

export default {
  data() {
    return {
      awesome: true
    }
  },
  methods: {
    toggle() {
      this.awesome = false
    }
  }
}

data() 에서는 데이터 값 배정을 : 로 했지만, method에서는 = 로 한다.

 

해당 이유는 아래와 같다. 

 

Vue 에서 data binding 과 method parameters 의 syntax 가 다르게 이용된다. 

 

ChatGPT 는 다음과 같이 대답한다. 

 

 

 

<form> 

The <form> element is used to group and organize multiple input elements into a form that can be submitted to a server for processing. It defines the boundaries of the form and specifies the destination URL where the data should be sent when the form is submitted. A form can contain various types of input elements such as text fields, checkboxes, radio buttons, dropdown menus, and buttons. 

 

<form> 은 원래 아래와 같이 사용 되며, action 에 적힌 URL 로 정보를 보낸다고 한다. 

<form action="/login" method="post">
  <label for="username">Username:</label>
  <input type="text" id="username" name="username"><br><br>
  <label for="password">Password:</label>
  <input type="password" id="password" name="password"><br><br>
  <input type="submit" value="Login">
</form>

그러나 prevent 를 이용하면 URL 을 보내는 defult 기능으로 사용되지 않고, 대신 custom action 을 할 수 있다. 

 

여기서는 그 action 이 "addTodo" 이다. 

 

<form> 안에 input, button 이 포함된다. 

input이 newTodo 데이터에 바인딩 되고 button 을 누르면 addTodo 가 호출된다.  

 

 <form @submit.prevent="addTodo">
    <input v-model="newTodo">
    <button>할 일 추가</button>
  </form>

 

button이 form @submit.prevent 를 trigger 한다는 어떠한 clue 도 적혀있지 않아 헷갈렸는데 이는 <form> element 안에 있기 때문에 trigger 로 동작한다. 

 

      this.todos = this.todos.filter((t) => t !== todo)

todos의 elemet 중 todo 와 다른 것들만 모아 todos 에 재배치하는 함수 

즉, todo 만 제거 

 

 

 

동적 바인딩 

 

더보기
조건이 true경우 클래스이름이 바인딩 되고 false경우 바인딩된 클래스이름이 제거 된다.

:class="{ '클래스이름' : '조건' }"

보통 class 에 거는건 CSS styling 을 위해서인데, 조건이 false 면 style이 반영되지 않는다.

 

Computed 

 

사실상 methods로 모두 대체할 수 있지만, 보통 템플릿의 데이터 표현을 더 직관적이고 간결하게 도와주는 속성이다. 

 

일단 계산이나 데이터 처리가 반복 될 경우를 위해 cache 하고 만약 데이터 속성에 변화가 있으면 이를 감지하고 자동으로 다시 연산하여 cache 한다. 또한 가독성도 높게 해준다. 가독성은 methods 와 computed 로 logic 이 seperate 되어있다보니 getFullname 이런 식으로 안하고 그냥 Fullname 이런 식으로 해도 된다. 

 

데이터 속성의 변화를 자동 감지하고 연산하다보니 HTTP 통신같이 컴퓨팅 리소스가 많이 필요한 로직은 정의하지 않는다. 

 

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

hideCompleted 가 True 면 !t.done filter, false 면 todos return 

 

 

생명주기 훅 ref 

 

이해한 바에 따르면 Vue는 DOM 을 최대한 직접 건드리지 않고 반응형, 선언적 렌더링으로 처리하고자  한다. 하지만 그 외의 경우 DOM을 수동으로 접근하여 처리하는 경우가 있다. 이 때, $ref 를 이용한다.

 

<script>
export default {
  mounted(){
  this.$refs.p.textContent = "마운트"
  }
}
</script>

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

 

ref 는 this. + $refs  로 접근한다. 또한 mounted() 선언을 통해 component가 렌더링 된 후에 접근이 가능해진다.  mounted 는 life cycle hook 을 명시할 때 사용한다. 보통 fetching data from API, setting up event listeners, initializing third party libraries 에 사용한다. 

정확한 용례는 component 가 처음 rended 되는 순간에 딱 한번 call 된다. 

 

Java script의 DOM node에 접근 가능한 property list 는 다음과 같다. 

 

  1. textContent
  2. innerHTML
  3. setAttribute()
  4. getAttribute()
  5. removeAttribute()
  6. style
  7. className
  8. classList

 

Dom Node

 

Watcher

지금까지 공부한 Vue 의 강점은 reactivity system 이다. 이로 인해 data property 에 변화가 있으면 감지하여 업데이트한다.

그럼에도 불구하고 watcher 를 쓰는 이유는 뭘까? 

 

watcher란? 

더보기

mechanism for reacting to changes in a specific data property or expression

 

일단 data가 변경될 때 마다 추적하는 건 너무 큰 computation resource 가 사용된다. 가령, watcher를 사용하면 복잡한 시스템이면 특정 dependency가 변경되었을 때만 trigger 되도록 할 수 있다.  

 

 또한 비동기 처리가 가능하다고 한다. 또한 property가 변할 때 특정 action 이나 side effect가 있도록 하기에 watcher 는 잘 설계 된 logic 이다. 또한 만약 property 들이 서로 의존적이며 업데이트가 같이 되야 한다면, watcher 를 사용하는 것이 답이다. 

 

Watcher의 사용법을 살펴보자. 

 

먼저 watch 하고 싶은 데이터 propety 를 결정한다. 

 data() {
    return {
      todoId: 1,
      todoData: null
    }

 

 

todoID 라고 할 때, Watcher 는 다음의 두가지 방법으로 비동기적으로 todoID 의 변화를 추적할 수 있다. 

 

watch: {
    todoId() {
    
    
watch: { 
	todoID: function(){

 

Data property 의 todoID 와 watcher의 함수 todoID() 는 당연히 다르다. 

 

 

side effect 

더보기

wacher 를 통해 데이터 변화가 추적 된 순간 하고 싶은 특정 action 등 ex) 날짜가 1일이 되면 월의 count를 ++ 하라. 

 

개발자들은 watcher 에는 다음과 같이 watcher function 만 놓고 side effect 는 따로 methods 에 빼놓곤 한다. 이는 두번째 용례였던 todoID: function() 과 같이 callback function 으로 사용하면 접근이 불가능하나 side effect 만 따로 method 에 빼놓게 되면 this. 로 접근이 가능하다. 

 

예시는 다음과 같다. 

<script>
export default {
  data() {
    return {
      todoId: 1,
      todoData: null
    }
  },
  methods: {
  },
  mounted() {
    this.fetchData()
  },
  watch: {
    todoId: async function() {
      this.todoData = null
      const res = await fetch(
        `https://jsonplaceholder.typicode.com/todos/${this.todoId}`
      )
      this.todoData = await res.json()
    }
  }
  
  
}
</script>

<template>
  <p>할 일 id: {{ todoId }}</p>
  <button @click="todoId++">다음 할 일 가져오기</button>
  <p v-if="!todoData">로딩...</p>
  <pre v-else>{{ todoData }}</pre>
</template>

 

watcher 안에 call back 함수를 선언하다보니 mounted() 에서 call 하고자 했던 fetchData 가 전혀 call 되지 못했다. 

 

이로 인해, 처음 코드가 돌아갈때는 this.fetchData() 가 call 되지 못해 error 가 발생한다. 그러나 그 다음부터 "다음 할 일 가져오기" 버튼을 눌렀을 때는 아무일도 없었던 것 처럼 동작한다. 

 

위 코드는 아래와 같이 methods 에 함수를 나눠 적음으로써 유연하게 코드를 짤 수 있다. 

 

<script>
export default {
  data() {
    return {
      todoId: 1,
      todoData: null
    }
  },
  methods: {
    async fetchData() {
      this.todoData = null
      const res = await fetch(
        `https://jsonplaceholder.typicode.com/todos/${this.todoId}`
      )
      this.todoData = await res.json()
    }
  },
  mounted() {
    this.fetchData()
  },
  watch: {
    todoId() {
      this.fetchData()
    }
  }
}
</script>

<template>
  <p>할 일 id: {{ todoId }}</p>
  <button @click="todoId++">다음 할 일 가져오기</button>
  <p v-if="!todoData">로딩...</p>
  <pre v-else>{{ todoData }}</pre>
</template>

 

Component

component를 불러와서 작업할 수 있다. 

 

import ChildComp from './ChildComp.vue'

export default {
  components: {
    ChildComp
  }
}
</script>

<template>
  <ChildComp />
</template>

 

import 는 <script> 안에서 이뤄져야 하고, component 를 사용하기 위해서는 <Componet name /> 을 언급해줘야 한다. 

 

Component 란?

더보기

vue file 에 작성 된 block 들은 모두 component 이다. 코드의 재활용이 용이한 형태로 되어있고 자식 component를 가져오는 이유이기도 하다. 이러한 의미에서 vue file 자체를 component 라고 할 수도 있다.

 

Vue 는 parent 가 child component 의 consumer 이다. 즉, child component 를 import 한다. 보통 언어들에서 parent 가 자식 class 에게 inheritnace 를 제공한다는 점에서 terminology가 특이하다. 

 

Props 

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

 

일단 선언되면 msg prop이 this 에 노출 되고, 자식 컴포넌트에서 템플릿 사용이 가능해진다. 

 

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

 

여기서는 또 자식 컴포넌트가 부모로부터 데이터를 pass down 받는다는 점에서 충분히 햇갈린다. 용례를 아래서 살펴보자 .

(Child 도 Parent Component 를 사용할 수 있는 방법이 있긴한데 지향되는 것으로 보인다.)

 

ParentCompoent.vue 

<template>
  <div>
    <h2>{{ title }}</h2>
    <ChildComponent v-for="item in items" :key="item.id" :item="item" />
  </div>
</template>

<script>
import ChildComponent from './ChildComponent.vue';

export default {
  components: {
    ChildComponent
  },
  data() {
    return {
      title: 'List of Items',
      items: [
        { id: 1, name: 'Item 1' },
        { id: 2, name: 'Item 2' },
        { id: 3, name: 'Item 3' }
      ]
    };
  }
}
</script>

 

ChildCompoent.vue

<template>
  <div>{{ item.name }}</div>
</template>

<script>
export default {
  props: ['item']
}
</script>

 

One by One 으로 살펴보자. 

더보기

 

ChildComponent tag 를 갖는 Parent Component, message 를 ChildComponet에 pass down 한다. 

 

 

Parent Component 에서는 Child Component 를 사용 가능하다. Child에게 보낼 message 를 설정했다. 

 

 

 Child Component 는 props 선언을 통해 Parent Component로 부터 data 를 받는다. 

 

 

 

 받은 데이터를 사용한다. 

 

 

Emits

child에서 이벤트와 데이터를 parent 로 발송할 수 있다. 

 

Child

export default {
  // emit할 이벤트 선언
  emits: ['response'],
  created() {
    // 인자와 함께 emit
    this.$emit('response', '자식 컴포넌트로부터 🌷를 받았어요!')
  }
}

emits 은 button 처럼 단순하고 emit 할 event 가 1개밖에 해당될 수 없는 경우를 제외하면 미리 선언해주어야 한다. 

emits: ['response']

 

button 같은 경우는 

<button @click="$emit('my-event', {data: 'some data'})">Click me</button>

Parent에서도 이렇게 심플하게 사용가능하다. 

 

Parent

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

 

created()

더보기

created() 도 lifecycle hook 의 일종이다. 

 

component instance 가 생성 되었지만 아직 mount 되지 않았을 때 call 된다. 

 

다른 components 들은 접근 가능하지만, DOM elements component 는 아직 mount 되기 전이라 접근 불가하다. 

 

이에 대한 chatGPT 의 답변은 다음과 같다. 

 

 

Slot

Parent component 는 이미 child component 를 직접 access 할 수 있기 때문에 slot 기능이 필요 없지만, 반대로 child 는 필요한 순간이 꽤 된다. Parent의 compoent를 동적으로 child component에 render 할 수 있다. 

예를 들어, parent component는 서로 스타일이 다른 여러개의 child component를 가질 수 있다. 각각의 child component가 동일한 text content를 보여야 한다면, slot 을 사용하여 child component가 unique style을 유지하도록 할 수 있다. 

 

예시는 아래와 같다. 

 

Parent 

<script>
import ChildComp from './ChildComp.vue'

export default {
  components: {
    ChildComp
  },
  data() {
    return {
      msg: 'Vue는 개발자에게 정말 유용하죠! 🎁'
    }
  }
}
</script>

<template>
  <ChildComp>부모로부터: {{ msg }}</ChildComp>
</template>

 

Child

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

 

 

이렇게 기본기는 마무리한다. 

Comments