본문 바로가기
Frontend/Vue 2

[vue] 정리 (최종 업데이트:2021-12-21)

by SOLYI 2021. 12. 7.
최솔이의 작성 요령
개념 개념 ?
명령어 $ npm ...
주석 //
플러그인, 팁 *
주제별로 언급 @

 


2021-12-07

VUE CLI / VUE 파일의 구성 요소 3가지

CLI ? Command-Line Interface 를 의미한다.
터미널을 통해 프로젝트를 손쉽게 시작할 수 있도록 도와준다.

$ npm install -g @vue/cli
// npm 이라는 명령어를 통해 vue cli를 전역(-g)에 설치해준다

* vue cli 공식 사이트
https://cli.vuejs.org/

$ vue create hello-world
// 간단하게 프로젝트를 생성 할 수 있다.

강의 내에서 간단한 프로젝트 생성은 건너 뛰고
lint ? 코드 작성의 규칙을 정함
eslint , rules 도 마찬가지

public/index.html 의 div id="app" 과 vuejs가 연결되어 이부분에 나타나게 된다.
src/main.js 의 createApp(App).mount('#app') <이 부분과 연결
src/App.vue 은 크게 3단계로 나누어져 있다.
1. <template>
-> html 내용 작성
2. import ~ export ~
-> 자바스크립트
3. <style>
-> CSS 내용 작성

src/components/HelloWorld.vue 를 살펴보면 마찬가지로 3단계 구성이다.
2번 export 부분에 vuejs의 문법으로 작성을 해서 1번 template 에 있는 html의 내용을 데이터로 만들어 화면에 출력 할 수 있다~~~

* VSCode에서 확장자 vetur 를 설치 해주면 알록달록하게 보임 ㅎㅎ


2021-12-08

WEBPACK TEMPLATE


WEBPACK ? vue cli, CDN 이 아닌 ~

$ npx degit ParkYoungWoong/webpack-template-basic 저장할폴더명
// 강사님의 깃허브 프로젝트를 폴더로 복제 한다는 의미
// 버전 관리 X

* git clone ~ // 버전 관리 O

기존 프로젝트는 vue 프로젝트가 아닌 html, css, javascript의 프로젝트 이므로 vue 프로젝트로 변경한다.
src/main.js 생성
src/App.vue 생성

$ npm i vue@next
// * npm i vue로 인스톨 한다면 2버전이 설치됨

$ npm i -D vue-loader@next vue-style-loader @vue/compiler-sfc
설치 완료 후 webpack.config.js 파일을 열어서 module.export = {} 내부의 module의
rules에 .vue로 끝나는 확장자를 검색하여 vue-loader 추가하고,
전에 작성해둔 scss 부분에 vue-style-loader 를 제일 위에 추가 해준다.

제일 상단으로 올라가서 const { VueLoaderPlugin } = require('vue-loader') 추가
module.export = {} 내부의 module 다음에 있는 plugin 내부에 new VueLoaderPlugin() 추가

module.export = {} 내부 가장 상단에 resolve : {} 내부에 extensions :['.js', '.vue'] 를 추가해주면 js파일과 vue 파일은 확장자를 입력하지 않아도 된다.

App.vue 파일로 들어가서
1. template 내부에 div 태그를 작성하고 {{ message }}
2. script 부분에 아래 태그를 추가한다

<script> export default { data() { return { message : 'Hello Vue!' } } } </script>


main.js 파일로 들어가서
import Vue from 'vue'
import App from './App.vue'

import Vue from 'vue' 
import App from './App.vue' Vue.createApp(App).mount('#app')

// 위, 아래 동일하게 동작한다 

// 객체 구조 분해하여 가져온 것 
import { createApp } from 'vue' 
import App from './App' 
// webpack.config.js 의 resolve - extensions 에
// '.vue'를 추가 했기 때문에 확장자 입력은 생략 가능
createApp(App).mount('#app')

// App.vue 파일이 vue 프로젝트의 시작.
index.html 파일의 body 태그 내부에 <div id="app"></div> 추가
이 내부에서 뷰 프로젝트가 동작 된다.

$ npm run dev
// 개발 서버 실행

위에서 작성한 Hello vue 가 표시 된다면 정상적으로 프로젝트가 실행되었다는 의미이다!

components/HelloWorld.vue 생성
1. template 내부에 <img src="~assets/logo.png" alt="" />

$ npm i -D file-loader

webpack.config.js 파일에서 rules의 js 아래에 다음을 추가한다.

{ test: /\.(png|jpe?g|gif|webp)$/, use: 'file-loader' }

resolve - extensions 다음에 alias를 추가한다. 경로의 별칭을 생성하는 것

 alias:{ 
   '~' : path.resolve(__dirname, 'src'), 
   'assets' : path.resolve(__dirname, 'src/assets')
} 
// 상대 경로를 가급적 사용하지 않도록 추가해준것. 
// App.vue 에서 다음과 같이 사용할 수 있다. 

import HelloWorld from '~/components/HelloWorld'
export default { 
  components: {
    HelloWorld  
  } 
  ...
} template 내부에 <HelloWorld/> 를 추가해주면 로고가 표시가 되어야 한다

 


2021-12-13

WEBPACK TEMPLATE - @ESlint

 

ESLint 구성 및 활용에 대해

 

$ npm i -D eslint eslint-plugin-vue babel-eslint

// -D 개발의존성 모드

eslint, eslint 플러그인 vue, 바벨 eslint 세가지를 다운로드 해준다.

 

.eslintrc.js 파일 생성

module.export = {
  env: {    //node환경에서 개발하고 browser에서 동작하므로 true로 설정
    browser: true,
    node: true,
  },
  extends: [   ///코드 규칙 명시
    //vue
    'plugin:vue/vue3-essential',
    'plugin:vue/vue3-storingly-recommended',
    'plugin:vue/vue3-recommended'
    //js
    'eslint: recommended'
    
  ],
  parserOptions: {},
  rules: {}
}


강도에 따라서 essential, vue3-stronly-recommended, vue3-recommended 세가지로 나뉜다.
vue3-recommended이 가장 엄격한 문법이며 essential 이 말그대로 필수 문법만 적용한다는 의미이다.

 


* ESLint 참고 사이트 : https://eslint.vuejs.org/user-guide/

 

기본적인 코드 규칙이 명시가 되어져있다.
규칙을 어길 시 에러가 발생하게됨
권장되는 코드 규칙들이므로 변경을 해줄수 있다.

 

* ESlint rules 사이트(설정 방법) : https://eslint.org/docs/rules/

parserOptions: {
  //코드 분석기 지정
  parser: 'babel-eslint',    // es6이상의 문법을 이전 버전에도 적용
}, 
rules: {
  // 위의 규칙을 그대로 따를 때에는 rules를 변경할 필요 없으나
  // 나만의, 우리 팀만의 규칙을 만들 때에는 추가 해준다.
}


하단 바에서 ESLINT를 사용하도록 설정을 변경한다.
[Allow Everywhere] 선택하여 모든 프로젝트에 적용한다.


빨간 밑줄로 오류가 발생 했을때 마우스를 올려보면 Disallow ~ .... eslint(vue/html-self-closing) 라고 표시가 된다.
사용을 하고싶다면 rules 부분에 html-self-closing를 추가해준다.

* ESLint 코드 작성 규칙  강도별로 확인하기, 추가하는 방법 : https://eslint.vuejs.org/rules/comment-directive.html

{
  "vue/html-self-closing": ["error", {
    "html": {
      "void": "never",
      "normal": "always",
      "component": "always"
    },
    "svg": "always",
    "math": "always"
  }]
}


등 개인, 팀의 취향에 맡게 코드 작성 규칙을 변경할 수 있다.

 


2021-12-14

선언적 렌더링과 입력 핸들링

vue라는 단일 파일에 SFC (Single File Component) 라고해서

HTML부분, 스크립트 부분, 스타일 부분 세가지를 한 파일에 작성할수있다.

 

Vue 파일의 제일 기초가 되는 문법은 다음과 같다.

<template>
  <h1 @click="increase"> 
    {{ count }} 
  </h1>
</template>

<script>
export default {
  data() {
    return {
      count : 0 
    }
  },
  methods: {
    increase(){
      this.count += 1
    }
  }
}
</script>

<style>
h1 {
  font-size: 50px;
}  
</style>

데이터를 갱신하면 화면도 갱신되는것을 반응성(Reactivity) 라고 한다.


조건문과 반복문 (if, for while) , @컴포넌트 Component

 

// 조건문 if
// Directive 디렉티브 
<div v-if="count > 5">
  5보다 큽니다!
</div>

// 반복문 for
// data() 내부에 배열 목록이 있을때
// fruits: ['Apple', 'Banana', 'Cherry'] 
<ul>
  <li 
    v-for="fruit in fruits"
    :key="fruit">
    {{ fruit }}
  </li>
</ul>

if 문은 v-if=" "

for 문은 v-for=" " :key=" "

를 사용한다.

 

Fruit.vue 파일을 만들어서 다음과 같이 코드를 작성한다.

<template>
  <li> {{ name }} </li
</template

<script>
  export default {
    props: {
      name: {
        type: String,
        default: ''
      }
    }
  }
</script>

위와 같이 작성한 것을 컴포넌트라고 한다.

이 컴포넌트를 위에서 사용한 App.vue 파일에서 변경해보도록 한다 .

// < 기존 >
<ul>
  <li 
    v-for="fruit in fruits"
    :key="fruit">
    {{ fruit }}
  </li>
</ul>


// < 컴포넌트로 변경 >
<ul>
  <Fruit 
    v-for="fruit in fruits"
    :key="fruit">
    {{ fruit }}
  </Fruit>
</ul>

// script의 data 부분에 다음과 같이 컴포넌트를 추가 해준다.
import Fruit from '~/components/Fruit'

export default {
  components: {
    Fruit: Fruit    // 속성의 이름과 데이터의 이름이 같으면 생략해줄수있다.  
  }
 , ...
}

 

컴포넌트 라는것을 만들어서 App.vue 혹은 컴포넌트간에 연결을 해서 웹사이트를 개발 할 수 있다.

 

* <style> 태그에  scoped를 추가해주면 다른 컴포넌트에는 영향을 미치지 않고, 해당 컴포넌트 내에서만 사용이 가능하다.


@인스턴스 @라이프사이클

참고 사이트 : vuejs 공식 사이트의 어플리케이션 인스턴스 생성하기

 

모든 Vue 어플리케이션은 createApp 함수를 사용하여 새로운 어플리케이션 인스턴스를 생성하여 시작합니다 .
인스턴스가 생성되면, mount 메소드에 컨테이너를 전달하여 mount 할 수 있습니다. 예를들어, <div id="app"></div>에 Vue 어플리케이션을 마운트 시키고 싶다면, #app을 전달해야합니다:
어플리케이션 인스턴스에 의해 노출된 대부분의 메소드들은 동일한 인스턴스를 반환하여 연결(chaining)을 허용합니다:

 

최상위 컴포넌트 Vue.createApp(RootComponent)  == App.vue

생성하는 모든 컴포넌트를 연결해서 사용

 

@ 라이프 사이클 훅

컴포넌트들은 생성될때 일련의 초기화 단계를 거친다. 데이터 관찰, 템플릿 컴파일, 마운트, 업데이트 등

 

created : 인스턴스가 생성된 후에 실행하는데 사용

mounted, updated, unmounted 등이 존재한다.

 

각 실행 순서는 다음 다이어그램을 참고할것.

 

beforeCreate : 이벤트, 라이프사이클이 초기화 된 후 실행

created : 인젝션, 리액티비티가 초기화 된 후 실행

템플릿 옵션 유무에 따라서 두갈래로 나뉜뒤

beforeMount : 연결되기 직전에 동작

mounted : 연결된 직 후에 동작.

beforeUpdate :  화면이 그려지기 직전에 동작

updated : 화면이 그려진 직후 동작

beforeUnmount : 연결이 끊기기 직전 

unmounted : 연결이 끊긴 후 동작

<script>
export default {
  data() {
    return {
      ...
    }
  },
  beforeCreate(){
  },
  created() {
  },
  beforeMount() {
  },
  mounted() {
  },
  beforeUpdate() {
  }
  updated() {
  }
}
</script>

각 동작이 언제 실행되는지 파악하고 있으면 좋을것 같다.

사용 빈도는 높지 않지만, 알고는 있어야한다.

 


@템플릿 문법 v-once / v-html / v-bind / v-on

참고 공식 사이트 : vuejs 템플릿 문법

 

보간법 

- 문자열 {{ msg }} 

<div> {{ msg }} </div>

- v-once : 1회성 문자열, 갱신 되더라도 화면이 바뀌지 않음

<div v-once> {{ msg }} </div>

- v-html : 아래 요기 부분을 실제 html 을 텍스트로 출력 할 수 있다.  

<p>v-html 디렉티브 사용 : <span v-html="rawHtml"> 요기 </span></p>

 

속성 v-bind  / v-on

뷰의 문법상 클래스와 아이디는 v-bind로 대체하여 표현한다.

v-bind:id 나 v-bind:class 등으로  id, class를 지원한다.

약어로 v-bind 를 생략하고 :id, :class 로도 표현 할 수 있다.

<template>
  <div v-bind:class="active"> 안녕하세요 </div>
</template>

<script>
export default {
  data() {
    return {
      msg: 'active'
    }
  }
}
</script>

<style scoped>
.active {
  color: royal-blue;
  font-size: 40px;
}
</style>

 

지난 강의에서도 사용했던 @click 또한  v-on:click의 약어이다.

 

또한 동적인 전달인자도 사용이 가능하다.

<div :[attr]="msg">안녕</div> 와 같이 작성하여 attr에 class를 부여하는 식으로 사용이 가능하다.

 

또한 기존에 @click="add" 으로 추가해주던 것도

@[evnet] 로 수정한뒤,  data 부분에서 event 에 click 을 할당해주는 방법도 있다.

<template>
  <div 
    :[attr]="msg"
   // msg 가 바뀌면 class 값 또한 msg!! 등으로 지속적으로 변경 될 가능성이 있다
   // 이런 경에는 msg 를 대신하여 문자열이라는 의미로 '' 를추가해준다
   // :[attr]="'active'"    
    @[evnet]="add"> 
    안녕하세요 
  </div>
</template>

<script>
export default {
  data() {
    return {
      msg: 'active',
      attr: 'class',
      event: 'click'
    }
  }
}
</script>

<style scoped>
.active {
  color: royal-blue;
  font-size: 40px;
}
</style>

@Computed 

 data() {
   ...
 },
 computed: {
   hasFruit() {
     return this.fruits.length > 0
   },
   reverseFruits() {
     return this.fruits.map(fruit => {
       return fruit.split('').reverse().join('')
       // split  한글자씩 잘라네어
       // reverse 뒤집은 다음
       // join 합친다.
     })
   }
 }

computed : data옵션에 정의해둔 특정 데이터들을 추가적으로 연산을 통해 정의를 한 다음, 그 값을 반환해서 사용할수 있는 데이터를 말한다. 계산된 데이터 라고 할 수 있다.

기존의 원본 데이터를 계산해서 사용을 해야할 때 computed 를 사용 할 수 있다.

 

@Computed  캐싱

methods 에 선언한 reverseMsg() 가, template 내에서 동일하게 4번 실행된다고 했을 때, 4번의 로직이 동작하게 된다.

그럴 때 computed 에 reversedMsg()를 선언하게 되면 화면에는 methods 를 4번 실행한것과 동일하게 표시가 되지만,

computed는 캐싱이라는 기능을 가지고 있어서 한번 연산을 했다면 저장된 값을 화면에 출력하므로 1번만 실행이 되게 된다. 결과 자체만 4번 실행하므로 부담이 되지 않는다.

 

 

 Computed 에서의 @Getter Setter 

computed에 계산해놓은 데이터는 READONLY 값이다. 반응형이 아니므로 내부의 로직으로 값을 얻어내는 용도로만 사용한다. 이것을 Getter 라고 한다.

 

computed에서는 기본적으로 값을 얻어내는것만 가능하고,

setter 의 경우 일반적으로 data() 내의 msg : '~' 부분에서 수정을 할 수도 있지만

computed에서도 setter기능을 이용 할 수 있다.

  data() {
    msg = 'Hello computed!'
  },
  computed: {
    //getter 와 setter 
    reversedMsg: {
      get() {
      	return this.msg.split('').reverse().join('')
      },
      set(newValue) {
      	this.msg = newValue	 // 새로운 값을 msg 에 할당
      }
    }
  },
  methods: {
    add() {
      this.reversedMsg += '?!' 
      // this.reversedMsg 의 계산된 데이터에 ?! 를 더해서 
      // set(newValue) 에서 newValue 로 받아서 다시 data의 msg 에 할당하게된다.
    }
  }

computed 에서  get, set 을 선언하는 이 방식이 일반적으로 흔하게 사용되지는 않지만 

후반에서 Vuex 를 학습 할 때 간간히 유용하게 사용 될 수 있으므로 알아두는 것이 좋다.


@Watch

watch : 특정한 데이터들의 변경사항을 감시하는 기능.

watch 내부에서 아래 코드와 같이 msg를 감시하도록 설정을 하면, msg 값이 변경될 때마다 console.log 가 동작한다.

  computed: {
    ...
  },
  watch: {
    msg: function() {
      console.log('msg: ', this.msg)
    }
    // 아래와 같이 :function 은 생략이 가능하다.
    msg() {
      console.log('msg: ', this.msg)
    }
  },  
  methods: {
    ...
  }

watch 내부에서 msg(value) 와 같이 매개변수 값을 지정해주면 "변경된 값"이 value 에 들어가는걸 확인할 수 있다.

 


2021-12-16

@클래스와 스타일 바인딩

<template>
  <div
    :class=" { active: isActive }"
    @click="activate">
    안녕하세요.
  </div>  
</template>

클래스 바인딩 : class 속성에 데이터를 연결해서 사용할 수 있다.

key: value 형태의 객체 데이터를 가질 수 있다. active라는 클래스를 isActive 라는 데이터로 연결을 하여 사용 할 수 있다.

 

div 태그에 active라는 클래스를 부여하여

isActive 값이 true라면 active 클래스가 추가되고, false 라면 active 클래스는 추가되지 않는다.

즉 isActive 값이 true 라면 <div class="active">  false 라면 <div class=""> 와 같이 클래스 값을 갖지 않는다.

 

하지만 꼭 {key: value} 형태의 인라인일 필요는 없다.

다음과 같이 객체데이터를 만들어서 연결해서 사용할수도있다.

<div :class="classObject"> 

data(){
  return {
    classObject: {
      active: true,
      'text-danger': false
    }
  }
}

 

다음과 같이 조건을 주어 계산된 데이터를 이용해 복잡한 형식으로도 사용을 할 수 있다.

computed: {
  return {
    classObject: {
      active: this.isActive && !this.error
      'text-danger': this.error && this.error.type === 'fatal'
    }
  }
}

 

또한 <div :class="[activeClass, errorClass]"> </div> 와 같이 [ 배열 ] 을 추가할 수도 있으며

<div :class="[isActive ? activeClass : '' , errorClass]"> 와 같이 삼항 연산자를 사용 할 수도 있다.
 
스타일 바인딩 : 인라인 방식으로 스타일을 표현 할 때 직접 바인딩 할 수 있다.
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }">안녕</div>

  data() {
    return {
      activeColor: 'red',
      fontSize: 40
    }
  }

주의할 점.

카멜 케이스 (fontSize) 로 작성 할 수도 있고 대시케이스(font-size)로도 사용이 가능하지만

대시케이스는 'font-size' 와같이 전후에 따옴표를 붙여주어야한다.

 

스타일 바인딩도 클래스 바인딩과 동일하게 [배열] 을 추가하여 사용이 가능하다.

 


@조건부 렌더링 v-if / v-show

위에서 조건문 If문에 대해서 학습했다.

타 언어와 마찬가지로  v-if 뿐만 아니라 v-else 문도 사용이 가능하다.

 

바로 뒤에 형제 요소가 있으면서 v-else 를 추가해주면 if문의 조건에 따라 표현이 가능하다.

조건문이 truthy 값인 경우에 v-if문, false인 경우에 v-else문만 출력하여 조건적으로 렌더링이 가능하다.

 

또한, v-else-if문도 사용 할 수 있다.

<button @click="handler">
  Click button
</button>

<div v-if="isShow">
  안녕하세요?
</div>
<div v-else>
  참 잘했어요
</div>

  data() {
    return {
      isShow: true
    }
  },
  methods: {
    handler() {
      this.isShow = !this.isShow
    }
  }

 

v-show 에 truthy, falsy 값을 주어 표현 할 수도 있는데  v-if 와의 차이점은 렌더링의 여부이다.

사용 방법은 동일하지만,  렌더링 차이가 있다.

v-if가 false 값일 때 해당 태그는 아예 렌더링을 하지 않고

v-show는 false값이더라도 해당 태그를 렌더링 하고 display: none 으로 표현한다. DOM 에 남아있다.

 

v-if: 게으르다. 조건이 false 라면 아무 작업도 하지않는다. 전환비용이 높음

v-show: 조건과 상관없이 항상 렌더링한다.  초기 렌더링 비용이 높음

 

자주 전환해야한다면 v-show

런타임시 조건이 변경되지 않는다면 v-if


@리스트 렌더링 / shortid / 변이 메소드 / 비변이 메소드

 

$ npm i -D shortid

index 를 대신하여 고유한 인덱스를 생성해주는 shortid를 사용 할 수 있다.

특정한 데이터들을 배열과 객체 데이터로 만들어야할때 고유한 값이 필요할때 shortid 패키지를 사용할 수 있다.

import shortid from 'shortid'

... 중략
  computed: {
    newFruits() {
      retrun this.fruits.map(fruit => ({
          id: shortid.generate(),
          name: fruit
      }))
    }
  }

 

또한 v-for 문에서 다음과같이 fruit을 객체 구조 분해 문법으로 활용할 수 있다.

<li
  v-for="{ id, name } " in newFruit"
  :key="id">
  {{ name }} - {{ id }}
</li>

 

공식 문서:  리스트 렌더링 - 배열 변경 감지

 

 

변이 메소드 : Vue는 감시중인 배열의 변이 메소드를 래핑하여 뷰 갱신을 트리거 합니다

 = 배열의 데이터를 변경한다면 화면에 갱신되어 출력이 되는 반응성을 가지고 있다는 의미.

 

push() - 배열 맨 뒤에 값 추가

pop() - 배열 맨 뒤 값 반환

shift() - 배열 맨 앞 값 반환

unshift() - 배열 맨 앞에 값 추가

splice() - 인덱스를 이용해서 데이터를 넣거나, 빼거나, 삭제 

sort() - 정렬

reverse() - 배열을 뒤집음

 

배열 교체 : 원래 배열을 변경하지 않고 새 배열을 반환하는 "비 변이" 메소드, 반응성은 가지고 있음

filter() - 원본 배열의 모든 요소들을 찾으면서 인수로 전달받은 콜백 함수를 반복 호출

concat() -  원본 배열 끝에 값 추가한 뒤 새로운 배열 반환

splice() - 원하는 범위의 값들을 복사하여 배열로 반환

 


@이벤트 핸들링

공식 문서 : 이벤트 핸들링

 

v-on:click 의 약어 @click

이전까지는 @click="count += 1"  과 같이 직접 스크립트를 작성했지만, 앞으로는 이벤트 핸들링을 이용한다.

이벤트 핸들링 :클릭이벤트가 발생했을때 실행할 함수 라는 의미

즉, @click="요기" 에는 methods 이름을 지정해주고, 실행 내용은 methods에 작성하기로 한다.

 

<button @click="handler">
  클릭
 </button>
 
 ...
   methods:{
     handler(event) {
       console.log(event)
     }
   }

handler 라는 메서드를 실행하면 event에 어떤 인자가 들어오는지 알아야한다.

이 경우, MouseEvent라는 이벤트 객체가 콘솔에 찍히게 된다. 

아래의 코드의 1번 내용에 해당.

1.
<button @click="handler"> 버튼1 </button>

2.
<button @click="handler()"> 버튼2 </button>

3.
<button @click="handler('msg')"> 버튼3 </button>

4.
<button @click="handler('msg', $event)"> 버튼4 </button>

5.
<button @click="handlerA(), handlerB()"> 버튼5 </button>

javascript 에서는 onClick:handler() 처럼 소괄호를 입력해주어야 했는데 (2번 코드)

vue에서는 소괄호 생략이 가능하고, 필요한 경우 handler('msg') 와 같이 인수를 추가해줄수 있다. (3번코드)

인수를 추가해주지 않으면 위와 같이 이벤트 객체가 콘솔에 찍히고, 인수를 추가해주면 msg 가 찍히게 된다.

 

이벤트 객체와 인수 둘다 사용하고 싶다면 @click="handler('msg, $event'") 와 같이 쓸 수 있다. (4번 코드)

 

5번과 같이, 각각의 여러개의 함수를 동시에 실행 시킬 수도 있다.

이 경우, 소괄호가 없으면 정상 작동하지 않으므로 주의해야한다.

 

@이벤트 핸들링 - 수식어

공식 문서 : 이벤트 수식어

 

위 이벤트 핸들링에서 methods 에서 event 로 인수를 받아와서 event객체를 콘솔에 찍는 등 이용할수 있었는데

vue에서 기본적으로 기능을 제공한다.

 

// 기존 방식
<a href="~" @click="handler" > 네이버 </a>

    handler(event) {
      event.preventDefault()
      console.log('abc')
    }
    
 // vue에서 제공하는 방식
<a href="~" @click.prevent="handler" > 네이버 </a>

    handler {
      console.log('abc')
    }

위의 코드와 아래 코드는 동일한 동작을 한다.

vue에서 단순화해서 기능을 제공한다.

 

.stop - (하단에 추가)

.prevent - 기본 동작을 동작 시키기 전에 중단 

.capture - (하단에 추가)

.self - (하단에 추가)

.once - 기본 동작은 여러 차례 반복 실행 가능하지만, 연결된 handler 라는 메서드는 단 한번만 실행이 가능

.passive - (하단에 추가)

 

 

체이닝 형태로 여러개를 붙여서 사용 할수도 있다.

a태그에 @click.prevent.once 추가시 : 처음 클릭시엔 콘솔만 찍히고, 두번째에는 href 로 이동만함

 

참고이미지

stop 개념

부모 div 내에 자식 div가 있을 때에, 자식 div를 클릭했는데 자식div 부모div둘다 클릭이 되는것을 이벤트 버블링이라고 하는데, 자식만 클릭하고싶을때 event.stopPropagation() 을 실행했었다. (이벤트 버블링 방지)

이와 같은 개념을 vue에서 stop으로 제공을 한다.

@click.stop="..."

 

capture 개념

이벤트 캡쳐링 : 부모 div를 클릭 했을때 자식 div도 클릭 되는, 이벤트 버블링의 반대 개념

부모div의 클릭 이벤트만 실행하고 싶다면 @click.capture.stop=".." 으로 작성할 수있다.

 

self 개념

부모 div 내에 자식 div가 있을때 온전히 부모 div만 선택했을때 클릭 이벤트를 발생시키기 위해서는

@click.self="..." 를 사용 할 수 있다. (자식 div클릭시 동작 안함)

정확하게 그 영역을 선택 했을때만 동작하게 한다.

 

passive 개념

@wheel="handler" 

예시로, wheel에 할당하는 핸들러에 과부하를 일으켰을때, 스크롤하는 화면과 로직에 버벅임이 발생하게 되는데

@wheel.passive="handler" 와같이 passive를 추가해주면 화면과 로직을 분리 시켜줄 수 있다.

화면은 부드럽게 움직이고, 로직은 따로 움직이게 되어 5배 가량 성능이 향상된다고 한다.

 

 


2021-12-18

@이벤트 핸들링 - 키 수식어

@keydown 에 메서드를 추가하면 키보드의 버튼을 하나씩 누를 때 마다 해당 메서드를 실행할수 있다.

또한 @keydown.enter ,  @keydown.a 등 엔터키나 a키를 눌렀을 때만 반응 할 수 있도록 설정도 가능하다.

@keydown.ctrl.a 등으로도 사용할 수있다.

<input 
  type="text"
  @keydown="handler" />

<input 
  type="text"
  @keydown.enter="handler" />

 


@폼 입력 바인딩 

<h1> {{ msg }} </h1>
<input
  text="text"
  :value="msg"
  @input="handler" />
  
export default {
  data() {
    return {
      msg: 'hello world'
    }
  },
  methods: {
    handler(event) {
      console.log(event.target.value)
      this.msg = event.target.value
    }
  }
}

위와 같이 input 태그에 입력한 내용을 실시간으로 h1태그에 출력 시킬 수 있다.

양방향 데이터 바인딩!

 

위 내용에서 input태그의 :value @input 기능을 합친 것으로 v-model 이라는 디렉티브를 이용하여 사용 할 수 있다.

<h1> {{ msg }} </h1>
<input
  text="text"
  v-model="msg" />
  
export default {
  data() {
    return {
      msg: 'hello world'
    }
  } 
}

 

한글의 경우 한 글자가 완성되기 전까지는 제대로 움직이지 않는다.

처음 방법처럼 :value="msg" @input "msg = $event.target.value" 로 작성해주면

자음과 모음으로 한글자가 완성되지 않더라도 반응형으로 동작하는 것을 알 수 있다.

 


2021-12-20

@v-model 수식어

v-model.trim 으로 작성하게 되면 앞과 뒤의 공백(띄어쓰기)는 자동으로 제거가 된다.


@ 컴포넌트

// App.vue

<MyBtn />

import MyBtn from '~/components/MyBtn'

export default {
  components: {
    MyBtn
  }
}

위 코드와 같이 MyBtn 을 임포트 해서 컴포넌트 영역에서 MyBtn을 선언한다.

 

//MyBtn.vue

<div class="btn>
  Apple
</div>

<style scoped> 
  .btn {
    color: white;
    background-color: gray;
  }
</style>

MyBtn 파일은 위와 같이 코드다.

 

컴포넌트의 장점은 여러 번 재활용 할 수 있다는 점이다.

MyBtn 을 4개 추가하는 과정에서, 동일한 스타일만 가지고 있는것이 싫다면

<MyBtn :color="blue"> 와 같이 색상을 파랑색으로 변경할 수 있는데, 그러기 위해서는 props를 알아야한다.

  // :color 라고 땡땡을 붙여준 이유는 color="blue"는 단순 문자열이기 때문에 데이터로 사용할수있도록 바인드로 써주어야한다.

 

MyBtn.vue 에서 script 부분에 아래와 같이 props - color 를 선언해준다.

값이 지정되어 있지 않다면 default 값을 사용한다.

<div 
  :style="{  background-color: color }"
  class="btn>
  Apple
</div>

export default {
  props: {
    color: {
      type: String,
      default: ''
    }
  }
}

 

위와 같은 코드를 작성하면 App.vue에서 color = "blue" 로 설정하였고, 

MyBtn.vue 에서는 :style을 사용해 배경 색상을 변경 할 수 있도록 props를 지정해두었기 때문에 배경 색상이 변경됨을 알 수 있다.

 

props라는것은, 마치 속성과 같이 컴포넌트를 실행할 때 외부에서 내용을 받아서 사용 할 수 있는 것이다.

 

props 를 이용해서 컴포넌트를 재사용하면서도 좀 더 확장해서 기능을 사용 할 수 있게 된다.

부모 컴포넌트와 자식 컴포넌트 간의 데이터 통신이 필요 할때 props를 사용하게 된다.

 

위에서 사용한 color 뿐만아니라, 사이즈 large, 문구 text, <slot> 도 사용할 수 있다.

 

<div 
  :class="{ large }"
  :style="{  background-color: color }"
  class="btn">
  <slot> </slot>
</div>

export default {
  props: {
    color: {
      type: String,
      default: ''
    },
    large: {
      type: Boolean,
      default: false
    },
    text: {
      type: String,
      default: ''
    }
  }
}

large, text 는 color 와 사용 방법이 동일하고, <slot></slot> 부분은 App.vue 에서 다음과 같이 사용이 가능하다.

<MyBtn> banana  </MyBtn> // slot 부분
<MyBtn
  large
  color="blue"
  text="banana" />

 

code 에서는 <slot>생성 전이라서 text 부분이 있지만, <slot>을 추가했다면 text 부분은 삭제해도 무방하다.

단, <MyBtn /> 이 아닌 <MyBtn> 내용 </MyBtn> 형식으로 변경 해주어야한다.

 

<slot> 부분에는 일반 텍스트문구 뿐만 아니라 <span style="color: red;"></span> 과 같이 다른 태그나 스타일을 부여할수있다.


@ 컴포넌트  - 속성 상속

위의 코드를 다시 사용한다.

App.vue에서 <MyBtn class="solyi> 로 클래스를 지정했을 때 개발자 도구로 확인을 해 보면,

화면에 보이는 코드가 기존 MyBtn.vue 의 <div class="btn"> 에 solyi 클래스가 추가되어있는것을 확인 해 볼 수 있다.

 

하지만 주의할 점으로, MyBtn.vue의 <div> 최상위 요소가 1개보다 많다면 class="solyi"는 적용 되지 않는다.

// MyBtn.vue

<template>
  <div class="btn>  // 최상위 요소는 이거 하나만 있어야 함.
  </div>
  
   // 이렇게 최상위 요소가 1개보다 많아지면 class="solyi" 는 사용 불가.
  <div> </div>  
</template>

style="color: red;" 등 과 같이 다른 속성도 추가해줄 수 있다.

마찬가지로 최상위 요소가 1개 보다 많다면 적용이 되지 않는다.

 

// MyBtn.vue

export default {
  inheritAttrs: false
}

위와 같이 상속을 false로 지정한다면, 최상위 요소가 하나만 있더라도 기본적인 속성의 내용을 상속하지 않는다.

(속성의 상속을 제거한다)

 

하지만, App.vue 에 작성한 class="solyi"를 적용하고 싶다면?

// MyBtn.vue

export default {
  inheritAttrs: false,
  created() {
    console.log(this.$attrs)
  }
}

위와 같이 created 된 직 후에 console 에 찍히는 내용을 살펴보면, Proxy라는 배열이 출력 된다.

그 중, target 내부의 class를 살펴 보면 solyi 가 찍혀있음을 확인할 수있다.

 

 

// MyBtn.vue

<template>
  <div class="btn>  // 최상위 요소는 이거 하나만 있어야 함.
  </div>
  <h1 v-bind="$attrs"> </h1>
</template>

고로, 위와 같이 v-bind에 "$attrs" 를 지정해주면 App.vue에서 작성한 class="solyi" 속성이 존재하게된다.

 

마찬가지로 class="solyi" 뿐만 아니라 style="color: red;" title="hello world" 등 을 추가 해줘서

<h1> 태그에는 별다른 속성을 부여하지 않았어도 App.vue의 MyBtn을 상속받아 표현을 해줄 수 있다.


2021-12-21

@ 컴포넌트  - emit

속성 상속의 내용과 유사하다.

App.vue 에서 <MyBtn @click="print"> </MyBtn>을 작성 할 때, MyBtn.vue의 최상위 요소가 복수라면, 정상 작동 하지 않는다.

// MyBtn.vue

<div class="btn">
  <slot></slot>
</div>
<h1 @click="$emit('click')">
  ABC
</h1>

export default {
  emits: [
    'click'
  ]
}

특정 이벤트를 상속 받아서 동작 시키고 싶다면 emits 배열에 이벤트를 작성하고, h1 태그에서 실행 할 수 있도록 $emit 메소드를 작성해서 실행 시켜 줄 수 있다.

 

App.vue에서 @click 이라는 이벤트를 사용하지 않고, @solyi라고 작성한다면

MyBtn.vue의 emits 에도 solyi, $emit 부분에도 'solyi'를 추가해서 사용해줄수있다.

원하는 이벤트 이름으로 만들어서 사용이 가능하다.

 

또한, 이벤트를 실행하는것 뿐만 아니라 데이터를 함께 넘겨 줄수도있다. 

아래와 같이 입력하면 12345 라는 데이터를 넘길 수 있다.

<h1 @click="$emit('click', 12345)">
  ABC
</h1>

 

또한 위에서 배운것과 마찬가지로 인수부분에 $event 를 작성하면 객체를 데이터로 전달 해줄수도 있다.

<h1 @click="$emit('click', $event)">
  ABC
</h1>

 

조금 더 깊은 내용을 알아 보기로 한다.

// MyBtn.vue

<div class="btn">
  <slot></slot>
</div>
<input 
  type="text"
  v-model="msg" />

export default {
  emits: [
    'click',
    'changeMsg'  // watch 에서 선언한 changeMsg 를 사용하기 위해 추가
  ],
  data() {
    return {
      msg: ''
    }
  },
  watch: {
    msg(){
     // changeMsg 라는 emit에 this.msg 를 인수로 담아 넘긴다
      this.$emit('changeMsg', this.msg)
    }
  }
}


// App.vue
  // changeMsg 가 아닌 change-msg 로 변경하여 작성해야한다.
  <MyBtn @change-msg="logMsg">	
    Banana
  </MyBtn>
  ...  
  methods: {
    logMsg(msg) {
      console.log(msg);
    }
  }

 

 

emit 을 통해서 원하는 이름의 이벤트를 실행 시켜줄수있다.

부모-자식 컴포넌트 간에 이벤트, 데이터를 주고 받을 수 있다.

 


@ 컴포넌트  - Slot 

MyBtn.vue 에서 작성했던 <slot>apple</slot> 부분에 기본 텍스트를 추가해 줄수 있다.

App.vue에서 새로 slot 내용에 banana를 작성한다면 banana라는 버튼이 만들어진다.

App.vue와 같이 슬롯 부분에 해당하는 문구가 없는 경우에는 대체 컨텐츠(Fallback Contents)가 출력 된다.

 

App.vue에 작성하는 슬롯 부분에는 일반 텍스트 뿐만 아니라 복수의 span 태그도 사용 할 수 있는데

이경우에 span 의 순서대로 버튼이 생성된다. ( <span> A </span> <span> B </span> 라면 A B가 출력 된다는 의미)

 

하지만 특정 슬롯 순서에 맞추고 싶다면 MyBtn.vue 내의 <slot name="icon"> 과 같이 이름을 추가해줘서 사용할수있다.

 

이 경우, App.vue 에서 단순하게 text만 입력했던 경우와는 다르게 template v-slot:icon 과 같이 사용해주어야한다.

  <MyBtn>
    <template v-slot:icon>
      <span> A </span>
    </template>
    <template v-slot:name>
      <span> B </span>
    </template>
  </MyBtn>

v-slot이라는 디렉티브는 약어 #으로 대체하여 사용이 가능하다.

 

짚고 넘어가기

 v-bind:  => :

 v-on:  => @

 v-slot => #

 

위 span 태그 내의 A, B 의 순서가 변경 되더라도, #icon, #name 이 slot 에서 지정이 되어 있다면

보장된 순서로 출력이 된다.

 

이름을 받는 슬롯 이라고 표현하기도 한다.

 


@ 컴포넌트  - Provide, Inject

위에서 배운 내용처럼 props 를 사용해서 부모-자식 컴포넌트 간에 데이터 통신이 이루어지긴 하지만,

만약 조상(App.vue) 에서 부모(parents.vue)을 거쳐 자식(child.vue) , 즉 조상 - 자식 간에 데이터를 주고 받을 때에는

부모 컴포넌트에 불필요한 props를 작성하게 된다.

 

이런 경우에는 Provide와 Inject 를 사용하면 바로 조상 - 자식 컴포넌트 간에 데이터 통신을 할 수 있다.

 

Provide : 제공[공급]하다, 주다

Inject : 주사하다, 주입하다, 더하다

 

// App.vue  (조상 컴포넌트)

  //기존 Parent
  <Parent :msg="message" />  
  
  // script 에 provide 를 추가해주므로써 
  // Parent 컴포넌트(부모)에 msg를 넘겨줄 필요가 없어졌으므로
  // :msg~ 부분을 생략한다. 
  <Parent />
    
  생략
  provide() {
    return {
      msg: this.message
    }
  }

Parents.vue(부모) 에서도 마찬가지로 :msg~ 부분과 스크립트 부분에 작성했었던 props 부분을 삭제 해주도록 한다.

 

Child.vue(자식) 에서도 props 부분을 삭제하고, 아래 inject 부분을 배열로 추가한다. 

  inject: [
    'msg'
  ]

조상 컴포넌트에서 msg 라는 이름으로 provide를 선언했으므로 inject 때에도 'msg'라는 같은 이름을 사용하도록 한다.

그렇게 되면 자식 컴포넌트에서 <div> {{ msg }} </div> 를 출력 할 수 있게 된다.

 

주의해야할 점으로, provide, inject 를 사용하게되면 반응성을 제공하지 않는다.

1. 단순하게 데이터를 1회성으로만 전달해서출력하거나

2. 반응성을 유지하게 작성하려면 추가 작업을 진행해야한다.

 

2의 반응성을 유지하도록 작성하는 방법으로 아래와 같은 방법이 있다.

// App.vue (조상)

import { computed } from 'vue'

...
  provide() {
    return {
      msg: computed(() => {
        return this.message
      })
    }
  }
...

// Child.vue (자식)

// 기존에는 아래와 같이 출력했다면,
<div> {{ msg }} </div> 

// provide에서 computed를 사용한 경우
// 아래와 같이 .value를 추가해주어야
// 이전과 동일한 출력 값을 확인할수 있다.
<div> {{ msg.value }} </div>

 

정리해보면 다음과 같다.

 

조상 -> 부모 : props

부모 -> 자식 : props

 

조상 -> 자식 : provide(조상), inject(자식)

 

실무에서 흔하게 쓰이는 경우는 아니지만, 이러한 개념이 있다는 것을 알아두면 좋다.

 


2021-12-24

@ 컴포넌트  - Refs

<template>
  <h1 id="hello">
    Hello World!
  </h1>
</template>

export default {
  mounted() {
    const h1El = document.querySelector("#hello");
    console.log(h1El);
  }
}

h1태그의 textContext를 출력하기 위해서 위와 같이 복잡하게 출력을 해야만 했다.

이번 시간에 학습할 ref를 사용하면 보다 간단하게 출력 할 수 있다.

<template>
  <h1 ref="hello">
    Hello World!
  </h1>
</template>

export default {
  mounted() {
    console.log(this.$refs.hello.textContext);
  }
}

ref를 hello로 지정해주면 this.$refs.hello 에서 그 값을 읽어올 수 있게 된다.

mounted 부분에서 선언한 이유는 template 부분이 마운트 된 후에나 읽을 수 있기 때문이다.

create() { } 에서는 읽어오지 못한다.


@ 컴포지션 API

@ 컴포지션 API : 구성요소 논리를 유연하게 구성할 수 있는 추가 기능 기반 API

 

한 페이지 내에 수많은 데이터와 methods, coumputed가 존재한다면 컴퓨터야 읽어내겠지만

개발자 입장에서는 영 읽기가 불편하다.

컴포지션 api를 사용하면 setup 함수 내부에서 데이터, 함수를 그루핑 할수 있어 보다 쉽게 데이터의 흐름을 파악할 수 있고 유지보수가 용이해진다. 또한 함수를 재사용하기가 용이해진다.

 

vue3부터는 기본 기능으로 제공한다.

React의 Hooks와 유사하다.

 

2021년 12월 24일 현재 vue3의 기본기능인 컴포지션 api는

상대적으로 중요도가 낮다고 판단되어 강의 내용 정리는 생략한다.

 

 

 

반응형

'Frontend > Vue 2' 카테고리의 다른 글

[vue2 - error] Vue packages version mismatch:  (0) 2022.01.31
[vue] eslint 가 아닌 prettier 가 적용 될 때  (0) 2021.12.25
[Netlify] 서버리스 배포  (0) 2021.10.22
[vue3] 동기? 비동기? promise  (0) 2021.10.21
[vue3] props, context  (0) 2021.10.19