컴포넌트 조합
- 부모 자식 관계 트리로 구성
- 전달방향은 주로 부모에서 자식으로 전달(단방향)
- 부모는 속성(props)으로 자식에게 전달, 자식은 부모에게 이벤트를 발신
- data, methods, computed, watch 의 Vue 인스턴스 옵션을 컴포넌트 수준에서 사용가능
- 컴포넌트에서의 data 옵션은 반드시 함수로 작성
- 컴포넌트 기반 개발시 data 옵션은 각 컴포넌트의 로컬 상태를 관리하기 위한 용도로 작성
- data 옵션을 단순한 객체값으로 사용하는 경우 모두가 동일한 값을 참조하기 때문
- 컴포넌트에서의 data 옵션은 반드시 함수로 작성
컴포넌트의 작성
Vue.component(tagname, options);
/*
tagname : 컴포넌트를 사용할 태그명
options : 컴포넌트에서 랜더링할 templet등을 지정
*/
live-server 설치
- npm install -g live-server
- vscode 에서 CTRL + ` 또는 view -> Terminal
예제
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<title></title>
<script src="https://unpkg.com/vue@2.5.16/dist/vue.js"></script>
</head>
<body>
<div id="app">
<hello-component></hello-component>
<hello-component></hello-component>
<hello-component></hello-component>
</div>
<script type="text/javascript">
Vue.component('hello-component', {
template: '<div>hello world</div>'
});
var simple1 = new Vue({
el: "#app",
data: {
boxstyle: {
boxcolor: true
}
}
});
</script>
</body>
</html>
템플릿 문자열 분리
<template id="helloTemplate">
<div>hello world</div>
</template>
<!--
또는
<script type="text/x-template" id="helloTemplate">
<div>hello world</div>
</script>
-->
<script type="text/javascript">
Vue.component('hello-component', {
template: '#helloTemplate'
});
</script>
DOM 템플릿 구문 작성 시 주의 사항
- 브라우저는 구문 분석하는 작업을 먼저 수행한 후 Vue 컴포넌트를 렌더링 하는데, 구문분석 단계에서 DOM 요소가 올바르지 못할 경우 제대로 렌더링 하지 못하는 문제가 발생
- 이 경우 is 특성을 이용하여 해결
- .vue (단일 컴포넌트)시 에도 문제 없음
- <script type="text/x-template"> 내에서도 문제 없음
<select>
<option-component></option-component>
<option-component></option-component>
</select>
<!--
<select>
<option is="option-component"></option>
<option is="option-component"></option>
</select>
또는
<script type="text/x-template" id="selectTemplate">
<select>
<option-component></option-component>
<option-component></option-component>
</select>
</script>
또는 .vue 파일 내에서 기술 하는 경우 해결
-->
<script>
Vue.component('option-component',{template : '<option>hello</option>'});
Vue.config.devtools = true;
var vm = new Vue({
el : '#app'
});
</script>
- template 태그로 감싸서 사용할 경우 selectTemplate 에서는 구문이 잘못된 select 태그를 사용하기 때문에 오류가 발생함
- <script type="text/x-template"> 를 사용할 경우는 문제가 없음
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<title></title>
<script src="https://unpkg.com/vue@2.5.16/dist/vue.js"></script>
<script>
Vue.component('option-component', {
template: '<option>hello</option>'
});
</script>
<template id="selectTemplate">
<select>
<option-component></option-component>
<option-component></option-component>
</select>
</template>
<!--
아래 형식으로 사용해야 오류가 없음
<script type="text/x-template" id="selectTemplate">
<select>
<option-component></option-component>
<option-component></option-component>
</select>
</script>
-->
<script>
Vue.component('select-component', {
template: '#selectTemplate'
});
</script>
</head>
<body>
<div id="app">
<select-component></select-component>
</div>
<script type="text/javascript">
var simple1 = new Vue({
el: "#app"
});
</script>
</body>
</html>
- 템플릿 문자열 안에서는 루트 요소 하나여야 함
<!-- 렌더링 되지 않음 -->
<template id="helloTemplate">
<div></div>
<div></div>
</template>
<!-- 렌더링 됨 -->
<template id="helloTemplate">
<div>
<div></div>
<div></div>
</div>
</template>
- IE 10, 11에서는 template 요소를 이용하면 template 요소들이 화면에 나타나는 현상이 있으므로 <script type="text/x-template">를 사용하여야 함
컴포넌트에서의 data 옵션
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<title></title>
<script src="https://unpkg.com/vue@2.5.16/dist/vue.js"></script>
<template id="timeTemplate">
<div>
<span>{{nowTS}}</span>
<button v-on:click="timeClick">현재 시간</button>
</div>
</template>
<script>
var d = {nowTS: 0};
Vue.component('time-component', {
template: '#timeTemplate',
/*
오류발생
data: { nowTS: 0 },
// 각 컴포넌트마다 d 를 공유하게 됨 권장하지 않음
data: function() { return d },
*/
data: function() {
return { nowTS: 0 }
},
methods: {
timeClick: function(e) {
this.nowTS = (new Date()).getTime();
}
}
});
</script>
</head>
<body>
<div id="app">
<time-component></time-component>
<time-component></time-component>
</div>
<script type="text/javascript">
Vue.config.devtools = true;
var v = new Vue({
el: "#app"
});
</script>
</body>
</html>
props와 event
태그에서의 케밥케이스 로 속성키 변경
<template id="listTemplate">
<li>{{ myMessage }}</li>
</template>
<script>
Vue.component('list-component',{
template : '#listTemplate',
props : ['myMessage']
});
</script>
....
<div id="app">
<ul>
<list-component my-message="Hello"></list-component>
<!--
카멜케이스는 오류발생
<list-component myMessage="니하오마"></list-component>
-->
</ul>
</div>
유효성 검증(문자, 숫자, 배열)및 숫자의 문자 리턴이 아닌 숫자리턴 방법
<script>
Vue.component('list-component',{
template : '#listTemplate',
props : {
message : { type: String, default : '안녕하세요?'},
count : {type: Number, requred : true},
countries : {
type : Array,
default: function() {
return ['대한민국']
}
}
}
});
</script>
....
<div id="app">
<ul>
<list-component message="Hello" count="100"></list-component>
<list-component message="니하오마" v-bind:count="21"></list-component>
<list-component message="니하오마"></list-component>
<list-component count="1"></list-component>
<list-component :countries="['중국','타이완']"></list-component>
</ul>
</div>
속성 사용
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<title></title>
<script src="https://unpkg.com/vue@2.5.16/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.10/lodash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fetch/2.0.4/fetch.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/es6-promise/4.1.1/es6-promise.auto.min.js"></script>
<style>
#list {
width: 400px;
border: 1px solid black;
border-collapse: collapse;
}
#list td,
#list th {
border: 1px solid black;
text-align: center;
}
#list>thead>tr {
color: yellow;
background-color: purple;
}
</style>
<template id="listTemplate">
<div>
<table id="list">
<thead>
<tr>
<th>번호</th>
<th>이름</th>
<th>전화번호</th>
<th>주소</th>
</tr>
</thead>
<tbody id="contacts">
<tr v-for="(contact, index) in contacts">
<td>{{contact.no}}</td>
<td>{{contact.name}}</td>
<td>{{contact.tel}}</td>
<td>{{contact.address}}</td>
</tr>
</tbody>
</table>
</div>
</template>
<script type="text/javascript">
Vue.component('contactlist-component', {
template: '#listTemplate',
props: ['contacts']
});
</script>
</head>
<body>
<div id="app">
<h1>예방 접종</h1>
<hr/>
<h3>1차 대상자 : 5월 1 ~ 3일</h3>
<contactlist-component :contacts="list1"></contactlist-component>
<h3>2차 대상자 : 5월 13 ~ 15일</h3>
<contactlist-component :contacts="list2"></contactlist-component>
</div>
</body>
<script type="text/javascript">
var vm = new Vue({
el: '#app',
data: {
list1: [
{no : 97, name:'A이름', tel:'010-1111-1111', address:'서울시'},
{no : 96, name:'B이름', tel:'010-1111-1112', address:'서울시'},
{no : 95, name:'C이름', tel:'010-1111-1113', address:'서울시'}
]
,list2: [
{no : 82, name:'D이름', tel:'010-1111-1115', address:'서울시'},
{no : 81, name:'E이름', tel:'010-1111-1114', address:'서울시'}
]
}
});
</script>
</html>
event를 이용한 정보 전달
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<title></title>
<script src="https://unpkg.com/vue@2.5.16/dist/vue.js"></script>
<style>
.buttonstyle {width: 120px;height: 30px;text-align: center;}
</style>
<template id="childTemplate">
<div>
<button class="buttonstyle" :data-lang="buttonInfo.value" @click="clickEvent">
{{ buttonInfo.text }}
</button>
</div>
</template>
<script>
var d = {
nowTS: 0
};
Vue.component('child-component', {
template: '#childTemplate',
props: ['buttonInfo'],
methods: {
clickEvent: function(e) {
this.$emit('time-click', e.target.innerText, e.target.dataset.lang);
}
}
});
</script>
<template id="parent-template">
<div>
<child-component v-for="s in buttons" :button-info="s" @time-click="timeClickEvent">
</child-component>
<hr/>
<div>{{ msg }}</div>
</div>
</template>
<script>
Vue.component('parent-component', {
template: '#parent-template',
props: ['buttons'],
data: function() {
return {msg: ''}
},
methods: {
timeClickEvent: function(k, v) {
this.msg = k + ', ' + v;
}
}
});
</script>
</head>
<body>
<div id="app">
<parent-component :buttons="buttons"></parent-component>
</div>
<script type="text/javascript">
Vue.config.devtools = true;
var v = new Vue({
el: "#app",
data: {
buttons: [
{text: 'hello',value: '영어'},
{text: '씬하오',value: '베트남어'},
{text: '니하오',value: '중국어'}
]
}
});
</script>
</body>
</html>
자식컴포넌트에서
this.$emit('time-click', e.target.innerText, e.target.dataset.lang);
부모컴포넌트에서
<child-component v-for="s in buttons" :button-info="s" @time-click="timeClickEvent">
</child-component>
....
<script>
....
methods: {
timeClickEvent: function(k, v) {
this.msg = k + ', ' + v;
}
}
</script>
props와 event 예제
틀 작성
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<title></title>
<script src="https://unpkg.com/vue@2.5.16/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.10/lodash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fetch/2.0.4/fetch.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/es6-promise/4.1.1/es6-promise.auto.min.js"></script>
</head>
<body>
</body>
<script type="text/javascript">
</script>
</html>
contactlist-component 작성
<style>
#list {
width: 400px;
border: 1px solid black;
border-collapse: collapse;
}
#list td,
#list th {
border: 1px solid black;
text-align: center;
}
#list>thead>tr {
color: yellow;
background-color: purple;
}
</style>
<template id="listTemplate">
<div>
<table id="list">
<thead>
<tr>
<th>번호</th>
<th>이름</th>
<th>전화번호</th>
<th>주소</th>
</tr>
</thead>
<tbody id="contacts">
<tr v-for="(contact, index) in contacts">
<td>{{contact.no}}</td>
<td>{{contact.name}}</td>
<td>{{contact.tel}}</td>
<td>{{contact.address}}</td>
</tr>
</tbody>
</table>
</div>
</template>
<script type="text/javascript">
Vue.component('contactlist-component', {
template: '#listTemplate',
props: ['contacts']
});
</script>
search-component 작성
<template id="searchTemplate">
<p>
이름<input type="text"
v-model.trim="name"
:placeholder="placeholder"
@keyup.enter="keyupEvent">
</p>
</template>
<script type="text/javascript">
Vue.component('search-component', {
template: '#searchTemplate',
props: ['placeholder'],
data: function() {
return {
name: ''
};
},
methods: {
keyupEvent: function(e) {
var val = e.target.value;
if (val.length >= 2) {
this.$emit('search', val);
} else {
this.$emit('search', '');
}
}
}
});
</script>
search-contact-component 작성
<template id="searchcontactTemplate">
<div>
<search-component placeholder="두 글자 이상 입력 후 엔터!" v-on:search="searchEvent">
</search-component>
<contactlist-component v-bind:contacts="contactlist"></contactlist-component>
<div v-show="isProcessing">조회중</div>
</div>
</template>
<script type="text/javascript">
Vue.component('search-contact-component', {
template: '#searchcontactTemplate',
data: function() {
return {
contactlist: [],
isProcessing: false
}
},
methods: {
searchEvent: function(name) {
if (name == '') {
this.contactlist = [];
} else {
this.fetchContacts(name);
}
},
fetchContacts: _.debounce(function(name) {
this.contactlist = [];
this.isProcessing = true;
var url = "http://sample.bmaster.kro.kr/contacts_long/search/" + name;
var vm = this;
fetch(url)
.then(function(response) {
return response.json();
})
.then(function(json) {
vm.contactlist = json;
vm.isProcessing = false;
})
.catch(function(ex) {
console.log('parsing failed', ex);
this.contactlist = [];
this.isProcessing = false;
});
}, 300)
}
});
</script>
body 작성
<body>
<div id="app">
<search-contact-component></search-contact-component>
</div>
</body>
<script type="text/javascript">
var vm = new Vue({
el: '#app';
})
</script>
이벤트 버스 객체를 이용한 통신
- 부모와 자식 관계가 아닌 컴포넌트의 통신이 필요 ( 형제관계, 부모손자관계 )
- 이벤트 버스 객체로 통신
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<title></title>
<script src="https://unpkg.com/vue@2.5.16/dist/vue.js"></script>
<script>
var eventBus = new Vue();
</script>
<template id="childTemplate">
<div>
<button v-on:click="clickEvent">child1 Button!!</button>
<div>{{currentTime}}</div>
</div>
</template>
<script>
Vue.component('child1-component', {
template: '#childTemplate',
data: function() {
return {
currentTime: ''
}
},
methods: {
clickEvent: function() {
var d = new Date();
var t = d.toLocaleTimeString() + ' ' + d.getMilliseconds() + 'ms';
eventBus.$emit('click1', t);
this.currentTime = t;
}
}
})
</script>
<template id="child2Template">
<ul>
<li v-for="t in timelist">{{t}}</li>
</ul>
</template>
<script>
Vue.component('child2-component', {
template: '#child2Template',
data: function() {
return {
timelist: []
}
},
created: function() {
eventBus.$on('click1', this.child1Click);
},
methods: {
child1Click: function(time) {
this.timelist.push(time);
}
}
})
</script>
</head>
<body>
<div id="app">
<child1-component></child1-component>
<hr/>
<child2-component></child2-component>
</div>
<script type="text/javascript">
Vue.config.devtools = true;
var v = new Vue({
el: "#app"
});
</script>
</body>
</html>
Todolist 실전 예제
기본틀과 이벤트 버스 객체 작성
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title></title>
<style>
* {
box-sizing: border-box;
}
.header {
background-color: purple;
padding: 30px;
color: yellow;
text-align: center;
}
.header::after {
content: "";
display: table;
clear: both;
}
</style>
<script src="https://unpkg.com/vue@2.5.16/dist/vue.js"></script>
<script>
var eventBus = new Vue();
</script>
</head>
<body>
<div id="todolistapp">
<div id="header" class="header">
<h2>Todo List app</h2>
</div>
</div>
<script type="text/javascript">
Vue.config.devtools = true;
var v = new Vue({
el: "#todolistapp"
});
</script>
</body>
</html>
list-component 추가
<style>
ul {
margin: 0;
padding: 0;
}
ul li {
cursor: pointer;
position: relative;
padding: 8px;
padding-left: 40px;
background: #eee;
font-size: 14px;
transition: .2s;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
ul li:hover {
background: #ddd;
}
ul li.checked {
background: #bbb;
color: #fff;
text-decoration: line-through;
}
ul li.checked::before {
content: '';
position: absolute;
border-color: #fff;
border-style: solid;
border-width: 0px 1px 1px 0px;
top: 10px;
left: 16px;
transform: rotate(45deg);
height: 8px;
width: 8px;
}
.close {
position: absolute;
right: 0;
top: 0;
padding: 12px 16px;
}
.close:hover {
background-color: #f44336;
color: #fff;
}
</style>
<template id="list-template">
<ul id="todolist">
<li v-for="a in todolist" :class="checked(a.done)" @click="doneToggle(a.id)">
<span>{{ a.todo }}</span>
<span v-if="a.done">완료</span>
<span class="close" @click.stop="deleteTodo(a.id)">×</span>
</li>
</ul>
</template>
<script>
Vue.component('list-component', {
template: '#list-template',
created: function() {
eventBus.$on('add-todo', this.addTodo);
},
data: function() {
return {
todolist: [{
id: 1,
todo: "영화보기",
done: false
}, {
id: 2,
todo: "주말산책",
done: true
}, {
id: 3,
todo: "ES6 학습",
done: false
}, {
id: 4,
todo: "잠실 야구장",
done: false
}]
}
},
methods: {
checked: function(done) {
if (done) return {
checked: true
};
else return {
checked: false
};
},
addTodo: function(todo) {
if (todo !== "") {
this.todolist.push({
todo: todo,
done: false
});
}
},
deleteTodo: function(id) {
var index = this.todolist.findIndex(function(item) {
return item.id === id;
});
this.todolist.splice(index, 1);
},
doneToggle: function(id) {
var index = this.todolist.findIndex(function(item) {
return item.id === id;
});
this.todolist[index].done = !this.todolist[index].done;
}
}
})
</script>
input-component 추가
<style>
.input {
border: none;
width: 75%;
height: 35px;
padding: 10px;
float: left;
font-size: 16px;
}
.addbutton {
padding: 10px;
width: 25%;
height: 35px;
background: #d9d9d9;
color: #555;
float: left;
text-align: center;
font-size: 13px;
cursor: pointer;
transition: .3s;
}
.addbutton:hover {
background: #bbb;
}
</style>
<template id="input-template">
<div>
<input type="text" class="input" id="task" placeholder="입력 후 엔터!" v-model.trim="todo" @keyup.enter="addTodo" />
<span class="addbutton" @click="addTodo">추 가</span>
</div>
</template>
<script>
Vue.component('input-component', {
template: '#input-template',
data: function() {
return {
todo: ''
}
},
methods: {
addTodo: function() {
eventBus.$emit('add-todo', this.todo);
this.todo = "";
}
}
})
</script>
컴포넌트 body 태그에 적용
<body>
<div id="todolistapp">
<div id="header" class="header">
<h2>Todo List app</h2>
<input-component></input-component>
</div>
<list-component></list-component>
</div>
<script type="text/javascript">
Vue.config.devtools = true;
var v = new Vue({
el: "#todolistapp"
});
</script>
</body>
'책 > Vue.js 퀵스타트' 카테고리의 다른 글
Vue-CLI 도구 (0) | 2020.11.22 |
---|---|
ECMAScript 2015 (0) | 2020.11.22 |
스타일 (0) | 2020.11.20 |
이벤트 처리 (0) | 2020.11.20 |
Vue 인스턴스 (0) | 2020.11.20 |