본문 바로가기

책/Vue.js 퀵스타트

컴포넌트 기초

컴포넌트 조합

  • 부모 자식 관계 트리로 구성
  • 전달방향은 주로 부모에서 자식으로 전달(단방향)
  • 부모는 속성(props)으로 자식에게 전달, 자식은 부모에게 이벤트를 발신
  • data, methods, computed, watch 의 Vue 인스턴스 옵션을 컴포넌트 수준에서 사용가능
    • 컴포넌트에서의 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)">&#x00D7;</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