Today I Learned ... Vue.js
🙋♂️ Reference Book
DAY 02 - 220529
- 컴포넌트
View, Data, Code의 세트.
-> HTML 코드, 자바스크립트 코드, 데이터가 존재함.
가장 큰 특징은 재사용 가능하다는 점.
다른 컴포넌트에서 임포트하여 사용할 수 있음.
컴포넌트는 그 페이지 자체일수도 있고, 페이지 내 특정 기능 요소일수도 있음.
src 폴더 내에 components
폴더에는 각 기능을 하는 요소가 들어있고,
views
폴더에는 페이지 기능인 vue 컴포넌트 파일이 들어있다.
-> 둘다 내부적으로 동일한 구조를 갖게 된다.
❗️ 폴더를 구분해서 사용하는 것이 효율적임.
+) 라이프사이클 메서드 (setup / created / mounted / unmounted / methods)
<template>
<div></div>
</template>
<script>
export default {
name: '',
components: {}, // 다른 컴포넌트 사용시 컴포넌트 임포트 후 배열로 저장함
data() {
return {
sampleData: '',
};
},
setup() {},
created() {},
mounted() {},
unmounted() {},
methods: {},
};
</script>
data
: html, js 코드에서 전역 변수로 사용하기 위한 데이터. (this를 통해 접근해야 함)
데이터 바인딩을 통해 해당하는 html과 js간의 양방향 통신이 가능하게 함.
methods
: 컴포넌트 내에서 사용할 메서드를 정의함. (this를 통해 접근해야 함)
setup
: 컴포지션 API를 구현
created
: 컴포넌트 생성시
mounted
: html 코드가 렌더링된 직후
unmounted
: 컴포넌트를 빠져나갈 시
{
"Generate Basic Vue Code": {
"prefix": "vue-start",
"body": [
"<template>",
"</template>",
"",
"<script>",
"export default {",
" name: '',",
" components: {},",
" data() {",
" return {",
" sampleData: '',",
" };",
" },",
" setup() {},",
" created() {},",
" mounted() {},",
" unmounted() {},",
" methods: {},",
"};",
"</script>",
""
],
"description": "Generate Basic Vue Code"
}
}
제일 먼저 보여줘야 하는 부분은 created()에 정의하여 서버로부터 미리 받아오고,
이후에 받아도 되는 부분은 mounted()에 정의하여 적절히 분배하면 좋음.
-> 로딩속도가 개선됨.
✅ 주의
모든 라이프사이클 훅은 자동으로 this 컨텍스트가 인스턴스에 바인딩되어 있으므로,
data, computed 및 methods 속성에 접근할 수 있음.
즉, 화살표 함수를 사용해서 라이프사이클 메소드를 정의하면 안됨. (this 바인딩 때문)
🙋♂️ 양방향 데이터 바인딩 이란?
Model
에서 데이터를 정의한 후View
와 연결시
둘중 한쪽에 변경이 일어났을 때 자동으로 한쪽에 반영됨.
src/views/DataBinding.vue 생성
<template>
<h1>Hello, {{ title }}</h1>
</template>
<script>
export default {
data() {
return {
title: 'Yjin',
};
},
};
</script>
data 프로퍼티에 정의된 title이 template의 {{title}}
에 바인딩 되는 구조
-> data에 정의되는 데이터는 이중 중괄호 {{ }}
로 html에 데이터 바인딩 할 수 있음.
router/index.js 수정
import { createRouter, createWebHistory } from 'vue-router';
import HomeView from '../views/HomeView.vue';
import DataBinding from '../views/DataBinding.vue';
const routes = [
{
path: '/',
name: 'home',
component: HomeView,
},
{
path: '/about',
name: 'about',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () =>
import(/* webpackChunkName: "about" */ '../views/AboutView.vue'),
},
{
path: '/databinding',
name: 'DataBinding',
component: DataBinding,
},
];
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes,
});
export default router;
<template>
<nav>
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link> |
<router-link to="/databinding">Data Binding</router-link>
</nav>
<router-view />
</template>
RESULT
data 프로퍼티의 title
에 설정한 'Yjin'이 잘 반영되어 있음을 알 수 있다.
✅ 디렉티브(directive) 란?
- HTML 태그 안에 들어가는 하나의 속성으로, 엘리먼트에게 동작을 지시하는 지시문임.
{{ }}
<h1>hello, {{title}}</h1>
v-html
디렉티브 사용<template>
<div>{{ htmlString }}</div>
<div v-html="htmlString"></div>
</template>
<script>
export default {
data() {
return {
htmlString: '<p style="color:red">Here is HTML string</p>',
};
},
};
</script>
-> 주의: <div v-html="htmlString">
에서 " " 안에 넣으면 된다. { }
아님.
v-model
디렉티브를 사용하여 데이터바인딩.v-model
은 서로 다른 속성을 사용하고, 서로 다른 입력 요소에 대해 서로 다른 이벤트를 전송.v-model
은 자동으로 value 속성을 사용하게 됨.<template>
<div>
<input type="text" v-model="valueModel" />
</div>
</template>
<script>
export default {
data() {
return {
valueModel: 'Lee Yjin',
};
},
};
</script>
-> 사용자가 텍스트를 입력하면 data.valueModel에 자동으로 입력한 텍스트가 저장이 된다.
-> 양방향 데이터 바인딩 때문.
리액트에서는 onChange 이벤트에 setState를 해서 직접 바꿔주어야 했음. (단방향 데이터바인딩)
문자가 아닌 숫자를 바로 처리할 수 있도록 v-model.number
디렉티브 사용 가능.
<template>
<div>
<input type="number" v-model.number="numberModel" />
</div>
</template>
<script>
export default {
data() {
return {
numberModel: 123,
};
},
};
</script>
textarea 태그도 <textarea>{{ message }}</textarea>
가 아닌
v-model
디렉티브를 사용해야 한다!
<template>
<div>
<textarea v-model="message"></textarea>
</div>
</template>
<script>
export default {
data() {
return {
message: 'text를 입력할 수 있는 태그입니다.',
};
},
};
</script>
v-model
은 내부적으로 select의 value 속성을 사용하여 양방향 데이터바인딩.
<template>
<div>
<select v-model="city">
<option value="01">서울</option>
<option value="02">부산</option>
<option value="03">제주</option>
</select>
</div>
</template>
<script>
export default {
data() {
return {
city: '03',
};
},
};
</script>
v-model은 내부적으로 체크박스의 checked
속성을 사용함 (true/false)
<template>
<div>
<label><input type="checkbox" v-model="checked" />{{ checked }}</label>
</div>
</template>
<script>
export default {
data() {
return {
checked: true,
};
},
};
</script>
+)
true-value
,false-value
를 설정해서 체크/체크해제시 기본값 설정 가능.
++) 여러개의 체크박스가 있다면 배열을 이용하여 데이터 바인딩 가능.
-> 한번에 처리 가능!<template> <div> <label><input type="checkbox" v-model="checked" value="사과" />사과</label> <label><input type="checkbox" v-model="checked" value="배" />배</label> <label><input type="checkbox" v-model="checked" value="포도" />포도</label> <label><input type="checkbox" v-model="checked" value="바나나" />바나나</label> <br /> <span>좋아하는 과일: {{ checked }} </span> </div> </template> <script> export default { data() { return { checked: [], }; }, }; </script>
마찬가지로 v-model
은 checked 속성과 바인딩이 이루어짐.
-> value 속성에 데이터 바인딩을 하려면 v-bind:value
를 사용해야 함.
<template>
<div>
<label><input type="radio" v-model="picked" v-bind:value="radioValue1" />사과</label>
<label><input type="radio" v-model="picked" v-bind:value="radioValue2" />배</label>
<label><input type="radio" v-model="picked" v-bind:value="radioValue3" />포도</label>
<label><input type="radio" v-model="picked" v-bind:value="radioValue4" />바나나</label>
<br />
<span>좋아하는 과일: {{ picked }} </span>
</div>
</template>
<script>
export default {
data() {
return {
picked: '',
radioValue1: '사과',
radioValue2: '배',
radioValue3: '포도',
radioValue4: '바나나',
};
},
};
</script>
= Attribute.
v-bind
디렉티브를 사용함.:
로도 사용 가능.<template>
<div>
<img v-bind:src="imgSrc" alt="image" />
</div>
</template>
<script>
export default {
data() {
return {
imgSrc:
'https://velog.velcdn.com/images/thisisyjin/profile/194fbad3-2c57-4ca5-89ad-7008fd5a455a/image.jpg',
};
},
};
</script>
v-bind를 생략하고 아래와 같이 콜론(:)만 써줘도 됨.
<img :src="imgSrc" alt="image" />
<template>
<div>
<input type="text" v-model="textValue" />
<button type="button" v-bind:disabled="textValue === ''">Click</button>
</div>
</template>
<script>
export default {
data() {
return {
textValue: '',
};
},
};
</script>
v-bind:class
로 입력해줌.ex>
<span v-bind:class="{
'active': isActive, 'error': isError
}">
<template>
<div>
<div
class="container"
v-bind:class="{
active: isActive,
'text-red': hasError,
}"
>
Class Binding
</div>
</div>
</template>
<script>
export default {
data() {
return {
isActive: true,
hasError: false,
};
},
};
</script>
<style scoped>
.container {
width: 100%;
height: 200px;
}
.active {
background-color: yellow;
font-weight: bold;
}
.text-red {
color: red;
}
</style>
+) 응용
<template>
<div>
<div
class="container"
v-bind:class="{
active: isActive,
'text-red': hasError,
}"
>
Class Binding
</div>
<label> <input type="checkbox" v-model="hasError" /> 에러 생성</label>
</div>
</template>
<script>
export default {
data() {
return {
isActive: true,
hasError: false,
};
},
};
</script>
<style scoped>
.container {
width: 100%;
height: 200px;
}
.active {
background-color: yellow;
font-weight: bold;
}
.text-red {
color: red;
}
</style>
-> checkbox의 v-model(=checked)에 hasError을 바인딩하고,
체크하면 hasError가 true가 되게 함.
✅ 참고 - style scoped
scoped
속성은 이 컴포넌트 안에서만 유효하는 style 속성
<template>
<div>
<div class="container" v-bind:class="[activeClass, errorClass]">
Class Binding
</div>
</div>
</template>
<script>
export default {
data() {
return {
activeClass: 'active',
errorClass: 'text-red',
};
},
};
</script>
v-bind:style
에 바인딩.<template>
<div>
<div :style="styleObj">Class Binding</div>
</div>
</template>
<script>
export default {
data() {
return {
styleObj: {
color: 'blue',
fontSize: '32px',
textDecoration: 'underline',
},
};
},
};
</script>
+) 배열로 스타일링 바인딩 가능
여러 스타일 객체를 배열에 담아 바인딩 할수도 있음.
<template>
<div>
<div :style="[basicStyle, addStyle]">Class Binding</div>
</div>
</template>
<script>
export default {
data() {
return {
basicStyle: 'color:blue;width:100%;height:200px;',
addStyle: 'font-weight:bold;background-color:lightyellow',
};
},
};
</script>
✅ 배열로 여러개의 스타일 객체를 적용할 때는 반드시 css 코드로 string으로 작성해야함!
cf> 하나 적용시 - Js 코드로 (camelCase + 객체)로 작성.
v-for
디렉티브를 이용하여 바인딩 가능<template>
<div>
<table>
<thead>
<tr>
<th>이름</th>
<th>나이</th>
<th>성별</th>
</tr>
</thead>
<tbody>
<tr :key="i" v-for="(person, i) in personList">
<td>{{ person.person_name }}</td>
<td>{{ person.age }}</td>
<td>{{ person.gender }}</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
export default {
data() {
return {
personList: [
{ person_name: 'yjin', age: 23, gender: 'female' },
{ person_name: 'yeon', age: 42, gender: 'male' },
{ person_name: 'kimj', age: 31, gender: 'male' },
{ person_name: 'thisis', age: 21, gender: 'female' },
],
};
},
};
</script>
v-for="(item, index) in 배열명"
과 같이 작성해줌.+) 참고 - 테이블 스타일링
<style scoped>
table {
margin: 0 auto;
border-collapse: collapse;
}
td,
th {
border: 1px solid black;
}
td {
padding: 8px 16px;
}
</style>
-> v-if / v-show
- 조건에 따라 렌더링 하는 방법
v-if
디렉티브 (+ v-else)v-show
디렉티브
<h1 v-if="h1Render">안녕하세요</h1>
-> h1Render 라는 값이 true면 렌더링 된다.
만약 false라면 else, else-if 를 이용해서 렌더링.
<template>
<div>
<h1 v-if="type === 'A'">A</h1>
<h1 v-else-if="type === 'B'">B</h1>
<h1 v-else>C</h1>
</div>
</template>
<script>
export default {
data() {
return {
type: 'A',
};
},
};
</script>
+) 응용 ver.
<template>
<div>
<label><input type="radio" v-bind:value="'A'" v-model="type" /> A</label>
<label><input type="radio" v-bind:value="'B'" v-model="type" /> B</label>
<label><input type="radio" v-bind:value="'C'" v-model="type" /> C</label>
<h1 v-if="type === 'A'">A</h1>
<h1 v-else-if="type === 'B'">B</h1>
<h1 v-else-if="type === 'C'">C</h1>
</div>
</template>
<script>
export default {
data() {
return {
type: '',
};
},
};
</script>
<h1 v-show="h1Show">안녕하세요</h1>
v-if | v-show |
---|---|
조건 만족시 html 블록 생성 / 불만족시 삭제 | 조건 만족 여부와 관계없이 html 블록이 생성됨. |
= 조건 만족시 display(css)를 이용하여 호면에 보이고, 조건 불만족시 숨기게 처리함.
🙋♂️ TIP - 언제 어떤 것을 사용할지?
v-show
는 자주 토글이 일어날때 사용한다.
(html 을 삭제/생성 하는것보다 css로 사라졌다 보였다 하는것이 훨씬 효율적이기 때문.
-> v-on
디렉티브
-> 심볼 @
로도 사용 가능.
v-on:click
또는 @click
을 사용함.<template>
<div>
<button @click="increaseCounter">Add +1</button>
<p>The Counter is {{ counter }}</p>
</div>
</template>
<script>
export default {
data() {
return {
counter: 0,
};
},
methods: {
increaseCounter() {
this.counter = this.counter + 1;
},
},
};
</script>
+) 메서드로 인자 전달도 가능
<template>
<div>
<button @click="setCount(7)">set 7</button>
<p>The Counter is {{ counter }}</p>
</div>
</template>
<script>
export default {
data() {
return {
counter: 0,
};
},
methods: {
setCount(number) {
this.counter = number;
},
},
};
</script>
++) 여러개의 함수 호출하려면?
-> 콤마(,)로 구분하여 호출함.
<template>
<div>
<button @click="one(), two()">Click!</button>
</div>
</template>
<script>
export default {
methods: {
one() {
alert('one');
},
two() {
alert('two');
},
},
};
</script>
참고 -
react
에서는 input에서 onChange 이벤트를 등록해주었지만,
양방향 데이터 바인딩을 지원하는 Vue에서는 그럴 필요가 없어짐.
<input type="text" @keyup.enter="submit">
-> 엔터키 눌렀을 때.
여러 키를 동싱 입력하거나 클릭+키입력을 동시에 했을때를 이벤트 등록할수도 있음.
<!-- Alt + Enter -->
<input type="text" @keyup.alt.enter="submit">
<!-- Ctrl + 클릭 -->
<input type="text" @click.ctrl="submit">
<template>
<div>
<h1>{{ fullName }}</h1>
</div>
</template>
<script>
export default {
data() {
return {
firstName: 'Yjin',
lastName: 'Lee',
};
},
computed: {
fullName() {
return this.firstName + ' ' + this.lastName;
},
},
};
</script>
-> 리액트의 useCallback(useMemo)와 비슷함.
❗️ computed는 함수이자 동시에 Vue 인스턴스의 데이터임.
-> 따라서 여러번 사용하더라도 함수 연산은 한번밖에 일어나지 않음.
+) 함수는 this.firstName, this.lastName이 변화했는지 감지할 수 없음.
<template>
<div>
<h1>이름 : {{ fullName }}</h1>
</div>
</template>
<script>
export default {
data() {
return {
firstName: 'Yjin',
lastName: 'Lee',
fullName: '',
};
},
watch: {
firstName() {
this.fullName = this.firstName + ' ' + this.lastName;
},
lastName() {
this.fullName = this.firstName + ' ' + this.lastName;
},
},
};
</script>
-> 초기 할당된 값이 변경이 일어나야만 watch가 실행됨.
+) 수정
<template>
<div>
<h1>이름 : {{ fullName }}</h1>
<label>이름<input type="text" v-model="lastName" /></label>
<label>성<input type="text" v-model="firstName" /></label>
</div>
</template>
<script>
export default {
data() {
return {
firstName: 'Yjin',
lastName: 'Lee',
fullName: '',
};
},
watch: {
firstName() {
this.fullName = this.firstName + ' ' + this.lastName;
},
lastName() {
this.fullName = this.firstName + ' ' + this.lastName;
},
},
};
</script>
-> input을 추가하여 둘중 하나라도 값을 바꿔야지만 watch가 실행됨.
computed | watch |
---|---|
기존 데이터 기반으로 새로운 데이터를 활용하기 위해 사용 | 데이터 값 하나만을 감시하기 위해 사용 |
데이터 변경이 없어도 처음에 실행됨 | 실제 데이터 변경이 일어나기 전까지는 실행되지 않음. |