개성있는 개발자 되기

[Vue.js] Ch6 컴포넌트 기초 본문

Web Development/Vue.js

[Vue.js] Ch6 컴포넌트 기초

정몽실이 2019. 5. 13. 21:41

Vue.js의 강력한 기능 중 하나는 컴포넌트이다. 대규모 애플리케이션을 작성할 때 컴포넌트의 장점이 있다.

 

  • 뛰어난 재사용성
    • 한 애플리케이션 내부에서 사용되는 UI와 기능은 반복되는 것이 꽤많다. 반복되는 부분을 컴포넌트(템플릿)로 작성해두면 여러 곳에서 재사용 가능하다.
  • 용이한 테스트
 

Mocha - the fun, simple, flexible JavaScript test framework

Mocha is a feature-rich JavaScript test framework running on Node.js and in the browser, making asynchronous testing simple and fun. Mocha tests run serially, allowing for flexible and accurate reporting, while mapping uncaught exceptions to the correct te

mochajs.org

  • 편리한 디버깅
    • Vue devtools와 같은 도구를 이용해 컴포넌트 단위로 전달된 속성을 손쉽게 살펴볼 수 있으며 부모 컴포넌트로 전달된 이벤트 정보를 손쉽게 파악할 수 있다.

1. 컴포넌트 조합

 

컴포넌트들은 부모-자식 관계로 트리 구조를 형성한다. 부모 컴포넌트가 자식 컴포넌트를 포함하는 형태이다.

<component-parent id="parent">
	<component-child id="child">
    </component-child>
</component-parent>

부모 컴포넌트가 자식 컴포넌트로 속성을 전달할 수 있으며, 자식 컴포넌트는 부모 컴포넌트로 이벤트를 발신할 수 있다.

자식 컴포넌트에서 이벤트를 정의하고 발생시키면 (이벤트 핸들러 메서드가 정의되어 있는) 부모 컴포넌트가 이를 인지해서 값을 전달받고 메서드를 수행하는 방법으로 상호작용을 한다.

 

data, methods, computed, watch 옵션과 같은 대부분의 Vue 인스턴스의 옵션을 컴포넌트 수준에서도 사용할 수 있다.

다만, data 옵션은 각 컴포넌트의 로컬 상태를 관리하기 위한 용도로만 사용한다. 즉, 컴포넌트의 data 옵션은 private 한다는 뜻이다.

따라서 컴포넌트의 data 옵션은 단순한 객체 값으로 작성하지 않고, 반드시 함수로 작성해서 함수 내부에서 객체를 리턴하도록 작성해야 한다.

// 에러
Vue.component("time-component", {
	template : "#timeTemplate",
    data : {nowTS : 0}
    }
})

// 정상
Vue.component("time-component", {
	template : "#timeTemplate",
    data : function() {
    	return {nowTS : 0}
    }
})

2. 컴포넌트의 작성

 

Vue.component(tagname, options)

tagname : 컴포넌트를 사용할 태그명 -> 케밥 표기법으로 작성한다. 예) tag-vue-component

options : 컴포넌트에서 렌더링할 templet 등을 지정한다.

 

2.1 인라인 템플릿

 

Vue 컴포넌트에 template 옵션을 템플릿 문자열을 사용해 작성하는 방식을 인라인 템플릿이라 한다.

    <script type="text/javascript">
        Vue.component('hello-component', {
            template: '<div>hello world!!!</div>'
        })
    </script>
    <div id="app">
        <hello-component></hello-component>
        <hello-component></hello-component>
        <hello-component></hello-component>
    </div>
    <script type="text/javascript">
        var v = new Vue({
            el: '#app'
        })
    </script>

 

2.2 템플릿 태그 (템플릿 문자열 분리)

 

위와 같이 인라인 템프릿 형식은 권장되지 않는다. 이 인라인 템플릿 문자열을 <template> 태그로 분리 시킨 후

Vue 컴포넌트에는 template 옵션에 해당 <template> id를 바인딩해준다.

<template id="helloTemplate">
    <div>hello world!!!</div>
</template>
<script type="text/javascript">
    Vue.component('hello-component', {
        template: '#helloTemplate'
    })
</script>

3. DOM 템플릿 구문 작성 시 주의 사항

 

템플릿 문자열(위의 2.1, 2.2 와 같은 방식)을 사용할 때 주의할 점이 있다.

 

3.1 브라우저 구문 분석 순서

브라우저는 스크립트를 읽어올때, 태그들의 구문 분석하는 작업을 먼저 수행한 후 Vue 컴포넌트를 렌더링한다.

따라서 사용자 정의해서 만든 컴포넌트를 구문 분석 단계에서 제대로 렌더링하지 못하는 문제가 발생할 수 있다.

 

<script type="text/javascript">
    Vue.component('option-component', {
        template: '<option>hello</option>'
    })
</script>

<body>
    <div id="app">
        <select>
            <option-component></option-component>
            <option-component></option-component>
        </select>
    </div>
</body>

렌저링 오류

위와 같이, 브라우저는 Vue 컴포넌트를 읽기전에 <select> 문구를 읽어와 <option-component>를 제대로 읽지 못하는 문제가 발생했다.

이 문제를 해결하기 위해서 is 특성(Attribute)을 이용한다.

    <div id="app">
        <select>
        <option is="option-component"></option>
        <option is="option-component"></option>
    </select>
    </div>

<script type="text/x-template"> 태그 안에서 사용되거나, vue 확장자를 사용하는 단일 파일 컴포넌트를 작성하는 경우에는 굳이 is 특성을 사용하지 않아도 된다.

단일 파일 컴포넌트에 대해서는 나중에 다루도록 한다.

 

3.2 루트 요소는 하나여야 한다.

템플릿 문자열 안에서 루트 요소는 하나여야 한다.

// 에러
<template>
	<div>hello</div>
    <div>world</div>
</template>

// 정상
<template>
	<div>
      <div>hello</div>
      <div>world</div>
    </div>
</template>

4. 컴포넌트에서의 data 옵션

컴포넌트 내부의 로컬 상태 정보를 저장하기 위해 data 옵션을 사용할 수 있다.

  • data 옵션에는 함수가 주어져야 한다.
    동일한 컴포넌트가 여러 번 사용되더라도 동일한 객체를 참조하는 것이 아니라, 함수가 호출될 때마다 만들어진 객체를 리턴해줘서 독립성을 지켜야한다.
    Vue.component('time-component', {
      template : '#timeTemplate',
      data : function() { 
          return { nowTS : 0 }
      },
      methods : {
          timeClick : function(e) {
              this.nowTS = (new Date()).getTime();
          }
      }
    })
  • data 옵션에 함수를 지정하더라도 모두 동일한 객체를 참도하도록 할 수 있다.
    전역 변수를 할당 해두고, 컴포넌트에서 전역 변수를 리턴해주는 것이다. 이렇게 하면 해당 템플릿을 쓰는 모든 컴포넌트에서 똑같은 nowTS 값을 가지게 될 것이다.
    책에서는 절대 이렇게 쓰지 말라고 권하고 있지만, 변하지 않는 final 속성을 가진 전역변수라면, 모든 컴포넌트에서 해당 전역변수에 접근하게 해서 기능을 해야 한다면 활용해도 좋을 듯하다.
var data = { nowTS : 0 };
Vue.component('time-component', {
    template : '#timeTemplate',
    data : function() {
        return data;
    },
    methods : {
        timeClick : function(e) {
            this.nowTS = (new Date()).getTime();
        }
    }
})

nowTS


5. props와 event

 

Vue 컴포넌트들이 부모-자식 관계로 형성되었을 때 각 컴포넌트 내부의 데이터는 캡슐화되기 때문에 다른 컴포넌트나 앱에서 접근할 수 없다.

▶ 속성(props)를 이용해서 부모에서 자식으로 단방향으로만 필요한 정보를 전달할 수 있다.

▶ 이벤트(event)를 이용해서 자식 컴포넌트에서 사용자 정의 이벤트를 필요한 정보와 함께 발신하면 부모 컴포넌트에서 v-on 디렉티브를 이용해서 이벤트를 처리할 수 있다.

 

5.1 props를 이용한 정보 전달

 

  • Vue 컴포넌트를 정의할 때, props라는 옵션을 작성하고 props명을 배열로 나열하면 된다.
// 컴포넌트 정의
<script type="text/javascript">
Vue.component('list-component', {
    template : '#listTemplate',
    props : [ 'message' ]    
})
</script>
<body>
<div id="app">
    <ul>
    	<!-- 속성 전달-->
        <list-component message="Hello"></list-component>
        <list-component message="씬짜오"></list-component>
        <list-component message="니하오마"></list-component>
    </ul>
</div>
  • 컴포넌트 작성 시, 속성명을 부여할 때는 카멜 표기법, 태그에서 속성명을 사용할 정보를 전달할 때는 반드시 케밥 표기법을 사용해야 한다.
<list-component myMessage="Hello"></list-component> <!-- 에러 -->
<list-component my-message="씬짜오"></list-component>
<list-component my-message="니하오마"></list-component>
  • 속성에 대한 Validation이 필요하다면 배열이 아닌, 객체 형태를 사용할 수 있다.
    속성값에 대한 type, default 값, 필수여부 값을 지정해 줄 수 있다.
Vue.component('list-component', {
    template : '#listTemplate',
    props : {
        message : { type:String, default:'안녕하세요' },
        count : { type:Number, required:true }
    } 
})

v-bind를 사용하지 않는다면, count 값이 type이 Number이더라도 String 타입으로 매핑된다.

<list-component message="Hello" v-bind:count="100"></list-component>
<list-component message="씬짜오" :count="21"></list-component>
<list-component message="니하오마"></list-component>
<list-component v-bind:count="1000"></list-component>
  • 속성으로 전달할 값이 배열이나 객체인 경우, 기본값을 부여하려면 반드시 함수를 사용해야 한다.
    Vue.component('list-component', {
        template: '#listTemplate',
        props: {
            message: {
                type: String,
                default: '안녕하세요'
            },
            count: {
                type: Number,
                required: true
            },
            countries: {
                type: Array,
                default: ['대한민국'] // 에러
            }
        }
    })

기본값을 부여하려면, 반드시 함수를 사용

5.2 event를 이용한 정보 전달

 

자식컴포넌트에서 이벤트를 발신하고, 부모 컴포넌트에서 v-on 디렉티브를 이용해 이벤트를 수신한다.

  • 부모, 자식 컴포넌트 작성
<!-- parent Component 시작 -->
<template id="parent-template">
    <div>
        <child-component v-for="s in buttons" v-bind:button-info="s" 
            v-on:timeclick="timeclickEvent">
        </child-component>
        <hr />
        <div>{{ msg }}</div>
    </div>
</template>
<script type="text/javascript">
Vue.component('parent-component', {
    template : '#parent-template',
    props : [ 'buttons' ],
    data : function() {
        return { msg:"" };
    },
    methods : {
        timeclickEvent : function(k, v) {
            this.msg = k + ", " +v;
        }
    }
})

<!-- child Component 시작 -->
<template id="childTemplate">
    <div>
        <button id="childBtn" class="buttonstyle" v-on:click="clickEvent" 
            v-bind:data-lang="buttonInfo.value">{{ buttonInfo.text }}</button>
    </div>
</template>
<script type="text/javascript">
Vue.component('child-component', {
    template : '#childTemplate',
    props : [ 'buttonInfo' ],
    methods : {
        clickEvent : function(e) {
            this.$emit('timeclick', e.target.innerText, e.target.dataset.lang);
        }
    }
})
</script>
<!-- child Component 끝 -->

[프로세스 정리]

child-component
 - childBtn 클릭 시, clickEvent 발생
   - clickEvent는 컴포넌트의 메소드 속성으로 정의되어 있음
     - 이벤트가 발생했을 경우 ('timeclick', e.target.innerText, e.target.dataset.lang) (사용자 정의 이벤트명, button 객체의 innerText, button 객체의 dataset.lang) 을 파라미터로 넘겨주고 있음
 - buttonInfo는 parent-component에서 속성 값으로 넘겨줌

 - data-lang 값에 buttonInfo.value 값을 바인딩 해줌

 

   - v-bind:data-lang = "buttonInfo.value", v-bind:data-temp= "buttonInfo.text"와 같이 작성하면

왼쪽과 같이 dataset이라는 DOMStringMap에 lang, temp 속성으로 값이 바인딩된다.



parent-component
  - 배열 buttons 만큼 for문으로 child component를 만들고 button-info 속성을 넣어줌
  - timeClick 이벤트 (자식 컴포넌트에서 호출한) 발생 시, timeClickEvent 메소드 속성 발생
    - 전달받은 파라미터 k, v를 출력

 

5.3 props와 event 예제

 

예제

[프로세스 설명]

search-component(자식1)에서 keyup 이벤트가 발생하면 search-contact-component로 (텍스트 문자열) 파라미터를 가진 이벤트를 발신한다.

search-contact-component(부모)에서 해당 문자열을 받아서 연락처 서비스 API를 이용해 연락처 목록을 받아온다.

받아온 연락처는 search-contact-component의 내부 상태로서 contactlist 데이터 옵션에 저장되고, 이것을 contacts 속성을 통해 contactlist-component(자식2)로 전달한다.


6. 이벤트 버스 객체를 이용한 통신

 

부모-자식 관계의 컴포넌트들은 앞에서 다룬 props와 events를 사용하면 된다. 하지만 부모-자식 관계가 아닌 경우에는 이벤트 버스 객체 즉, 비어 있는 Vue 인스턴스를 만들어서 사용하면 된다.

 

  • 이벤트를 수신하는 컴포넌트미리 이벤트 핸들러를 등록해야 한다.
    Vue 인스턴스 생명주기의 created 이벤트를 이용해서 Vue 인스턴스가 만들어질 때, $on 메서드로 이벤트 수신 정보를 등록해둔다. 이벤트를 발신하는 컴포넌트에서는 $emit 메서드를 호출한다.
<!-- 이벤트 버스 객체 -->
<script type="text/javascript">
	var eventBus = new Vue();
</script>
<!-- 두번째 자식 컴포넌트 시작-->
<template id="chidl2Template">
    <ul>
        <li v-for="t in timelist">{{t}}</li>
    </ul>
</template>
<script type="text/javascript">
Vue.component('child2-component', {
    template : '#chidl2Template',
    data : function() {
        return { 
            timelist : []
        };
    },
    created : function() {
        eventBus.$on('click1', this.child1Click);
    },
    methods : {
        child1Click : function(time) {
            this.timelist.push(time);
        }
    }
});
</script>
<!-- 두번째 자식 컴포넌트 끝-->
<!-- 첫번째 자식 컴포넌트 시작-->
<template id="chidl1Template">
    <div>
        <button v-on:click="clickEvent">child1 button!!</button>
        <div>{{currentTime}}</div>
    </div>
</template>
<script type="text/javascript">
Vue.component('child1-component', {
    template : '#chidl1Template',
    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>
<!-- 첫번째 자식 컴포넌트 끝-->

[프로세스 설명]

child1Template
  - clickEvent 발생 시, eventBus객체로 ("click1", 시간) 파라미터 전송

child2Template
  - Vue 인스턴스 created 시, eventBus 객체에 "click1" 이벤트 바인딩
  - child1Template에서 click1이 전송 되면, timelist 배열에 time 을 push

'Web Development > Vue.js' 카테고리의 다른 글

[Vue.js] Ch8 Vue-CLI 도구  (0) 2019.06.13
[Vue.js] Ch7 ECMAScript 2015  (0) 2019.06.06
[Vue.js] Ch5 스타일 적용  (0) 2019.05.09
[Vue.js] Ch4 이벤트 처리  (0) 2019.05.02
[Vue.js] Ch3 Vue 인스턴스  (0) 2019.04.25
Comments