티스토리 뷰
이번 포스트에서는 SPA에서 가장 중요하다 이야기 되는 컴포넌트에 관해 이야기 할 것입니다.
1. 기본 예제
// Define a new component called button-counter
Vue.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: '<button @click="count++">You clicked me {{ count }} times.</button>'
});
위의 예제는 <button-counter>
라는 이름으로 재사용이 가능한 컴포넌트입니다.
<div id="app">
<button-counter><button-counter/>
</div>
Vue.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: '<button @click="count++">You clicked me {{ count }} times.</button>'
});
new Vue({
el: '#app'
});
위의 예제와 같이 컴포넌트를 사용할 수 있습니다. 컴포넌트를 생성할 때 사용되는 옵션은 new Vue
에 사용되는 옵션(data
, computed
, watch
, methods
...)과 동일 합니다. 단 하나의 예외는 컴포넌트에는 el
옵션을 사용할 수 없다는 것입니다.
위의 코드의 결과는 CodePen에서 확인 할 수 있습니다.
2. 컴포넌트 재사용
<div id="app">
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
</div>
Vue.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: 'You clicked me {{ count }} times.'
});
new Vue({
el: '#app'
});
컴포넌트는 원하는 만큼 여러번 재사용이 가능합니다. 위의 예제의 버튼들은 클릭 될 때 각각의 count
값을 나타냅니다. 컴포넌트를 사용할 때 마다 컴포넌트의 인스턴스가 생성되기 때문에 컴포넌트 마다 서로 다른 count
를 가지게 되는 것입니다.
위의 예제 결과는 CodePen에서 확인 할 수 있습니다.
* 참고 - 컴포넌트의 data
옵션은 반드시 함수형이어야 합니다.
new Vue
에서 옵션으로 넘겨주는 data
는 객체형, 함수형 모두 가능합니다. 하지만 컴포넌트에서 data
는 반드시 함수형이여야 합니다.
3. 컴포넌트 구조
Vue는 흔히 컴포넌트가 트리 형태로 중첩되어 사용됩니다.
예를 들면, 헤더와 사이드바, 콘텐츠 영역으로 나뉘며, 콘텐츠 영역은 포스트 등.. 각각의 영역은 다른 컴포넌트로 구성되어 집니다.
Vue의 template에 컴포넌트가 사용되기 위해서는 Vue가 알 수 있도록 컴포넌트가 등록되어야 합니다. 전역으로 사용할 수 있는 글로벌 방식, 해당인스턴스에서만 사용할 수 있는 로컬 방식 두가지 방법으로 컴포넌트를 등록 할 수 있습니다.
Vue.component('my-component-name', {
// ... options ...
})
위의 방법을 사용하여 글로벌 방식으로 컴포넌트를 등록할 경우, 모든 Vue 인스턴스에서 컴포넌트를 사용 할 수 있게 됩니다.
var ComponentA = { /* ... */ }
var ComponentB = {
components: {
'component-a': ComponentA
},
// ...
}
import ComponentA from './ComponentA.vue'
export default {
components: {
ComponentA
},
// ...
}
위의 방법을 사용하여 로컬 방식으로 컴포넌트를 등록할 경우, 컴포넌트를 등록한 Vue 인스턴스에서만 컴포넌트를 사용할 수 있습니다.
4. props
사용하기
부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달하기 위해 흔히 사용 됩니다. props
는 data
와 유사하게 사용 가능한 속성입니다.
Vue.component('blog-post', {
props: ['title'],
template: '<h3>{{ title }}</h3>'
})
위의 예제와 같이 props
를 정의하면 정의한 이름으로 template에 사용 가능합니다. props
로 원하는 만큼 전달하여 사용할 수 있으며, 모든 값을 props
로 전달 가능합니다.
<blog-post title="My journey with Vue"></blog-post>
<blog-post title="Blogging with Vue"></blog-post>
<blog-post title="Why Vue is so fun"></blog-post>
부모 컴포넌트에서는 위의 예제와 같이 자식 컴포넌트로 데이터 전달을 할 수 있습니다.
<div id="app">
<blog-post
v-for="post in posts"
v-bind:key="post.id"
v-bind:title="post.title"
></blog-post>
</div>
Vue.component('blog-post', {
props: ['title'],
template: '<h3>{{ title }}</h3>'
})
new Vue({
el: '#app',
data: {
posts: [
{ id: 1, title: 'My journey with Vue' },
{ id: 2, title: 'Blogging with Vue' },
{ id: 3, title: 'Why Vue is so fun' }
]
}
});
위의 예제와 같이 v-for
와 props
를 함께 사용하면 코드 중복 없이 간결하게 코딩이 가능합니다.
위의 예제는 CodePen에서 확인 할 수 있습니다.
5. 단일 루트
컴포넌트는 단일 루트로 감싸져야 합니다. 이전에 이야기 하면서 예를 들었던 <blog-post>
로 이야기 하자면,
<h3>{{ title }}</h3>
<blog-post>
컴포넌트는 위의 코드와 같이 title
만 나타내는 하나의 엘리먼트만 가지고 있습니다.
<h3>{{ title }}</h3>
<div v-html="content"></div>
여기에 content
를 나타내는 엘리먼트를 추가하기 위해 위의 예제와 같이 작성하면 에러가 발생하게 됩니다.
<div class="blog-post">
<h3>{{ title }}</h3>
<div v-html="content"></div>
</div>
컴포넌트는 위의 예제와 같이 단일 루트로 감싸져야 합니다.
<blog-post
v-for="post in posts"
v-bind:key="post.id"
v-bind:title="post.title"
v-bind:content="post.content"
v-bind:publishedAt="post.publishedAt"
v-bind:comments="post.comments"
></blog-post>
위의 예제와 같이 컴포넌트가 커지고, 많은 데이터를 컴포넌트로 넘겨주어야 할 때 모든 데이터를 props
로 넘겨주는 것은 비효율적일 수 있습니다.
<div id="app">
<blog-post
v-for="post in posts"
v-bind:key="post.id"
v-bind:post="post"
></blog-post>
</div>
Vue.component('blog-post', {
props: ['post'],
template: `
<div class="blog-post">
<h3>{{ post.title }}</h3>
<div v-html="post.content"></div>
</div>
`
})
new Vue({
el: '#app',
data: {
posts: [
{ id: 1, title: 'My journey with Vue', content: '1st' },
{ id: 2, title: 'Blogging with Vue', content: '2nd' },
{ id: 3, title: 'Why Vue is so fun', content: '3th' }
]
}
});
컴포넌트는 단일 루트 엘리먼트여야 합니다.
여러 데이터를 자식 컴포넌트로 전달해야 할 때 전달되어야 할 모든 값을 props
로 전달 하는 것이 아니라, 위의 예제와 같이 하나의 객체로 묶어 props
로 넘기는 것이 효율적입니다. 나중에 추가로 값을 전달 해야 할 경우, props
를 추가하는 것이 아니라 객체에 값을 추가하여 넘겨 주는 것으로 코드 변경을 최소화 할 수 있습니다.
위의 예제는 CodePen에서 확인 할 수 있습니다.
6. 부모 컴포넌트로 데이터 전달
자식 컴포넌트에서 부모 컴포넌트로 데이터를 전달해야 할 때가 있습니다. 그 때 사용되는 것이 $emit
입니다.
예를 들어, <blog-post>
컴포넌트에서, 텍스트 확대 기능을 추가하기 위해 부모 컴포넌트로 텍스트 확대를 했다는 이벤트를 부모 컴포넌트로 전달해야 한다고 가정해 봅니다.
<div id="blog-posts-events-demo">
<div :style="{ fontSize: postFontSize + 'em' }">
<blog-post
v-for="post in posts"
v-bind:key="post.id"
v-bind:post="post"
></blog-post>
</div>
</div>
new Vue({
el: '#app',
data: {
posts: [
{ id: 1, title: 'My journey with Vue', content: '1st' },
{ id: 2, title: 'Blogging with Vue', content: '2nd' },
{ id: 3, title: 'Why Vue is so fun', content: '3th' }
],
postFontSize: 1
}
});
부모 컴포넌트는 위의 예제와 같이 작성 될 수 있습니다. postFontSize
로 텍스트의 크기가 정의 됩니다.
Vue.component('blog-post', {
props: ['post'],
template: `
<div class="blog-post">
<h3>{{ post.title }}</h3>
<button>Enlarge text</button>
<div v-html="post.content"></div>
</div>
`
})
자식 컴포넌트는 위의 예제와 같이 작성하면, <button>
이 눌려젔을 때 부모 컴포넌트의 postFontSize
의 값이 변경 되어야 합니다.
<button @click="$emit('enlarge-text')">Enlarge text</button>
postFontSize
를 변경해야 한다는 것을 부모 컴포넌트에 알려 주기 위해 위의 코드와 같이 $emit
을 사용합니다.
<blog-post
v-for="post in posts"
v-bind:key="post.id"
v-bind:post="post"
@enlarge-text="postFontSize += 0.1"
></blog-post>
부모 컴포넌트에서는 자식 컴포넌트에서 전달한 이벤트를 핸들링 하기 위해 위에 코드와 같이 @enlarge-text
라는 이벤트 핸들러를 정의해야 합니다.
위의 예제는 CodePen에서 확인 할 수 있습니다.
1) $emit
으로 부모 컴포넌트에 데이터 전달하기
$emit
메소드의 두번째 파라미터를 사용하면, 부모 컴포넌트로 데이터 전달이 가능합니다.
<button @click="$emit('enlarge-text', 0.1)">Enlarge text</button>
위의 예제는 부모 컴포넌트로 enlarge-text
라는 이벤트를 통해 0.1의 값을 전달하는 예제입니다.
<blog-post
v-for="post in posts"
v-bind:key="post.id"
v-bind:post="post"
@enlarge-text="postFontSize += $event"
></blog-post>
부모 컴포넌트에서는 자식 컴포넌트로 부터 전달 받은 값을 위의 예제와 같이 $event
를 통해 사용 가능합니다.
<div id="app">
<div :style="{ fontSize: postFontSize + 'em' }">
<blog-post
v-for="post in posts"
v-bind:key="post.id"
v-bind:post="post"
@enlarge-text="onEnlargeText"
></blog-post>
</div>
</div>
Vue.component('blog-post', {
props: ['post'],
template: `
<div class="blog-post">
<h3>{{ post.title }}</h3>
<button @click="$emit('enlarge-text', 0.1)">Enlarge text</button>
<div v-html="post.content"></div>
</div>
`
})
new Vue({
el: '#app',
data: {
posts: [
{ id: 1, title: 'My journey with Vue', content: '1st' },
{ id: 2, title: 'Blogging with Vue', content: '2nd' },
{ id: 3, title: 'Why Vue is so fun', content: '3th' }
],
postFontSize: 1
},
methods: {
onEnlargeText: function (enlargeAmount) {
this.postFontSize += enlargeAmount
}
}
});
일반 네이티브 이벤트와 동일하게 이벤트 핸들러를 사용할 수도 있습니다.
위의 예제는 CodePen에서 확인 할 수 있습니다.
2) 컴포넌트에서 v-model
사용하기
<input v-model="searchText">
input
태그에서 v-model
을 위의 코드와 같이 사용할 경우,
<input
v-bind:value="searchText"
v-on:input="searchText = $event.target.value"
>
위의 코드와 같은 동작을 하게 됩니다.
<custom-input
v-bind:value="searchText"
v-on:input="searchText = $event"
></custom-input>
value
attribute는custom-input
의value
prop와 바인딩 됩니다.input
이벤트는custom-input
에서$emit
으로 호출되는 이벤트입니다.
위와 같이 작성이 된 코드는,
Vue.component('custom-input', {
props: ['value'],
template: `
<input
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
>
`
})
위와 같이 다시 작성 될 수 있습니다. 위의 코드는 또 다시,
<custom-input v-model="searchText"></custom-input>
위의 코드와 같이 작성될 수 있습니다. 컴포넌트에서 v-model
을 사용하는 방법은 [Vue.JS] 컴포넌트 (고급:Custrom Events) 에서 더 자세히 이야기 하도록 하겠습니다.
7. slot
사용하기
<alert-box>
Something bad happened.
</alert-box>
컴포넌트를 사용할 때, HTML 엘리먼트와 같이 위의 코드처럼 컴포넌트에 콘덴츠를 전달해야 할 때가 있습니다. 그럴 때 사용해야 하는 것이 slot
입니다.
Vue.component('alert-box', {
template: `
<div class="demo-alert-box">
<strong>Error!</strong>
<slot></slot>
</div>
`
})
alert-box
컴포넌트가 위의 코드와 같이 코딩되어 있다면,
<alert-box>Somthing bad happend.</alert-box>
의 예제 결과는 위의 사진과 같습니다. 위의 예제 코드 결과는 CodePen에서 확인 할 수 있습니다. slot
에 대한 좀 더 자세한 이야기는 [Vue.JS] 컴포넌트 (고급:slot) 에서 이야기 하도록 하겠습니다.
8. 동적 컴포넌트
동적으로 컴포넌트를 전환해야 할 때가 있습니다. 그럴 때 사용하기 유용한 것이 <component>
와 is
입니다.
<!-- Component changes when currentTabComponent changes -->
<component v-bind:is="currentTabComponent"></component>
currentTabComponent
의 리턴 값은 컴포넌트의 이름이나 컴포넌트 객체입니다. 위의 예제는 Fiddle에서 확인 할 수 있습니다. 동적 컴포넌트에 관한 좀 더 자세한 내용은 [Vue.JS] 컴포넌트 (고급:Dynamic & Async Components) 에서 이야기 하도록 하겠습니다.
9. DOM 템블릿 문법 경고 해결
<ul>
, <ol>
, <table>
, <select>
등의 HTML 태그들의 자식 태그로는 <li>
, <tr>
, <option>
등으로 항상 정해져 있습니다. 이러한 규칙을 어기게 되면 DOM은 경고를 뱉어내가 됩니다.
<table>
<blog-post-row></blog-post-row>
</table>
예를 들어, 위의 코드와 같이 작성된 코드가 있다고 할 때, <table>
의 자식 태그들은 <tr>
등.. 이 와야 하지만 그렇지 않기 때문에 DOM은 경고를 발생시킵니다. <blog-post-row>
가 <tr>
태그로 구성되어 있다고 해도 경고를 피해 갈 수 없습니다. DOM이 발생하는 경고를 피하기 위해 is
를 사용할 수 있습니다.
<table>
<tr is="blog-post-row"></tr>
</table>
위와 같이 is
를 이용하거나,
- template 속성을 사용한다 (e.g.
template: '...'
) .vue
파일로 컴포넌트를 생성한다.<script type="text/x-template">
를 사용한다.
위의 3가지 방법을 사용하면 DOM의 경고를 피할 수 있는 방법이 있습니다.
참고
'Vue.JS' 카테고리의 다른 글
[Vue.JS] 컴포넌트 (고급:Custrom Events) (0) | 2019.01.24 |
---|---|
[Vue.JS] 컴포넌트 (고급:props) (2) | 2019.01.21 |
[Vue.JS] 폼 입력 바인딩 (0) | 2018.12.15 |
[Vue.JS] 이벤트 핸들링 (0) | 2018.12.06 |
[Vue.JS] 리스트 렌더링 (0) | 2018.11.25 |