티스토리 뷰
1. 이벤트 이름
$emit
을 이용하여 커스텀 이벤트를 만들 때, 이벤트 이름은 kebab-case를 사용하는 것이 좋습니다.
컴포넌트의 이름이나 prop의 이름과 달리 이벤트 이름은 자동으로 변환(camelCase <-> kebab-case)되지 않습니다. 이벤트 이름은 정확히 일치하는 이벤트 리스너와 매칭됩니다. 예를 들어,
this.$emit('myEvent')
위의 코드와 같이 myEvent
이벤트를 발생 시킨다고 할 때, 부모 컴포넌트는
<my-component v-on:my-event="doSomething"></my-component>
위의 코드와 같이 myEvent
를 받기 위해 v-on:my-event
를 사용한다면, my-event
는 myEvent
가 발생 했다는 것을 감지 할 수 없습니다. 컴포넌트의 이름이나 prop의 이름과 다르게 이벤트 이름은 자바스크립트의 변수나, 속성으로 사용되지 않기 때문에 이벤트 이름은 camelCase나 PascalCase로 작성될 이유가 없습니다.
HTML에서는 모든 글자를 소문자로 인식하기 때문에 v-on:myEvent
는 결국 v-on:myevnet
와 동일하게 인식됩니다. 결국 this.$emit('myEvent')
로 myEvent
를 발생 시켜도 이벤트를 감지하기 못하게 됩니다.
결론만 간단히 이야기 하자면, 커스텀 이벤트의 이름은 kebab-case를 사용해야 합니다.
2. 컴포넌트에서 v-model
사용하기
2.2.0 이상의 Vue 버전에서 추가된 기능입니다.
v-model
은 value
속성과 input
이벤트를 함께 사용하는 것과 같습니다. 즉. v-model
을 사용한다는 것은 input
이벤트가 발생 했을 때, value
값을 변경하는 것과 동일합니다. 하지만 체크박스나 라디오 버튼의 경우 이벤트가 발생 했을 때, value
값을 변경하는 것이 아닌 checked
의 값을 바꿈으로 동작합니다. 체크박스와 라이오 버튼에서 value
는 다른 목적으로 사용됩니다. 이 때 model
옵션을 사용하면, 위에서 이야기한 문제를 해결 할 수 있습니다.
Vue.component('base-checkbox', {
model: {
prop: 'checked',
event: 'custom-change'
},
props: {
checked: Boolean
},
template: `
<label>
<input
type="checkbox"
v-bind:checked="checked"
v-on:change="$emit('custom-change', $event.target.checked)"
>{{ checked }}
</label>
`
})
위의 코드와 같이 작성된 컴포넌트가 있다면 부모 컴포넌트에서 v-model
은,
<base-checkbox v-model="lovingVue"></base-checkbox>
위의 코드와 같이 사용할 수 있습니다.
lovingVue
의 값은 props
의 checked
로 값이 전달 됩니다. 그리고 자식 컴포넌트에서 change
이벤트가 발생되면, $emit('custom-change')
가 실행되고, $emit
의 두번째 인자로 전달된 값으로 lovingVue
의 값이 업데이트 됩니다. 자식 컴포넌트에서 model
옵션의 prop로 정의 된 checked
는 컴포넌트의 props
에 동일한 이름으로 정의 되어야 합니다.
위의 예제는 CodePen에서 확인 할 수 있습니다.
3. 컴포넌트에 네이티브 이벤트 바인딩하기
컴포넌트의 루트 엘리먼트에서 네이티브 이벤트를 받기 위해서는 v-on
이벤트 리스너에 .native
수식어를 사용하면 됩니다.
<base-input v-on:focus.native="onFocus"></base-input>
위의 코드와 같이 루트 엘리먼트에 네이티브 이벤트 리스너를 등록하면, 유용할 수 있지만, 특정 엘리먼트에는 좋은 방법이 아닐 수 아닙니다.
<label>
{{ label }}
<input
v-bind="$attrs"
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
>
</label>
예를 들어, <base-input>
컴포넌트의 위의 코드와 같을 경우, <base-input>
컴포넌트의 루트 엘리먼트는 <label>
이 되고, <label>
은 focus
이벤트를 핸들링하지 못하기 때문에, 에러는 발생하지 않지만, v-on:focus.native="onFocus"
은 동작하지 않습니다.
이 문제를 해결하려면 $listeners
속성을 사용하는 것이 좋습니다.
{
focus: function (event) { /* ... */ }
input: function (value) { /* ... */ },
}
$listeners
는 위의 코드와 같이 이벤트 리스너를 담고 있는 Object
입니다. $listeners
속성을 사용하면 모든 이벤트 리스너를 특정 자식 엘리먼트로 전달 할 수 있습니다.
Vue.component('base-input', {
inheritAttrs: false,
props: ['label', 'value'],
computed: {
inputListeners: function () {
var vm = this
// `Object.assign` merges objects together to form a new object
return Object.assign({},
// We add all the listeners from the parent
this.$listeners,
// Then we can add custom listeners or override the
// behavior of some listeners.
{
// This ensures that the component works with v-model
input: function (event) {
vm.$emit('input', event.target.value)
}
}
)
}
},
template: `
<label>
{{ label }}
<input
v-bind="$attrs"
v-bind:value="value"
v-on="inputListeners"
>
</label>
`
})
4. .sync
수식어
.sync
수식어는 2.3.0 이상의 Vue 버전에서 사용할 수 있습니다.
때때로 부모 컴포넌트와 자식 컴포넌트가 양방향 데이터 통신을 해야 할 때가 있습니다. 하지만 양방향 데이터 통신은 유지보수를 어렵게 만들 수 있습니다. 자식 컴포넌트에서 부모 컴포넌트의 값을 수정하기 때문에, 부모 컴포넌트에서는 어떠한 자식 컴포넌트가 값을 수정한 것인지 파악하기 어렵게 될 수 있습니다. 그렇기 때문에 자식 컴포넌트에서 update:myPropName
을 이용하여 이벤트를 부모 컴포넌트로 전달하는 방법으로 양방향 데이터 통신을 구현할 것입니다.
자식 컴포넌트에서 title
prop를 업데이트 하려고 할 때,
this.$emit('update:title', newTitle)
위의 코드와 같이 $emit
을 사용하여 부모 컴포넌트로 데이터를 전달 합니다. 그러면 부모 컴포넌트는,
<text-document
v-bind:title="doc.title"
v-on:update:title="doc.title = $event"
></text-document>
위의 코드와 같이 :update:title
를 사용하여 자식 컴포넌트로 부터 데이터를 전달 받습니다. 이와 같은 동작을 .sync
수식어를 통하여,
<text-document v-bind:title.sync="doc.title"></text-document>
위의 코드와 같이 간편하게 구현 할 수 있습니다.
* 참고 - v-bind
와 .sync
를 이용하여 양방향 데이터 통신을 할 때 유의사항
v-bind:title.sync="doc.title + '!'"
와 같은 표현식은 정상 동작하지 않습니다. v-model
와 유사하게 property 이름만 전달 되어야 합니다.
.sync
도 v-bind
를 사용하여 객체를 통째로 props
로 전달 하는 것이 가능합니다.
<text-document v-bind.sync="doc"></text-document>
위의 코드와 같이 사용하면 v-bind
에 객체를 넘기는 것과 동일한 동작을 합니다. 즉 v-bind.sync
에 객체를 넘기면 객체에 있는 모든 값들이 prop로 인식됩니다.
* 참고 - v-bind.sync
를 사용할 때 주의 사항
v-bind.sync="{ title: doc.title }"
과 같이 사용할 수 없습니다. 파싱을 할 때 너무 많은 예외 케이스들이 발생하여 이와 같은 표현식은 사용할 수 없습니다.
참고
'Vue.JS' 카테고리의 다른 글
[Vue.JS] 컴포넌트 (고급:Dynamic & Async Components) (0) | 2019.01.29 |
---|---|
[Vue.JS] 컴포넌트 (고급:slot) (0) | 2019.01.26 |
[Vue.JS] 컴포넌트 (고급:props) (2) | 2019.01.21 |
[Vue.JS] 컴포넌트 (기본) (3) | 2019.01.16 |
[Vue.JS] 폼 입력 바인딩 (0) | 2018.12.15 |