티스토리 뷰

Vue.JS

[Vue.JS] 컴포넌트 (고급:Custrom Events)

버미노트 2019. 1. 24. 01:12

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-eventmyEvent가 발생 했다는 것을 감지 할 수 없습니다. 컴포넌트의 이름이나 prop의 이름과 다르게 이벤트 이름은 자바스크립트의 변수나, 속성으로 사용되지 않기 때문에 이벤트 이름은 camelCase나 PascalCase로 작성될 이유가 없습니다.

HTML에서는 모든 글자를 소문자로 인식하기 때문에 v-on:myEvent는 결국 v-on:myevnet와 동일하게 인식됩니다. 결국 this.$emit('myEvent')myEvent를 발생 시켜도 이벤트를 감지하기 못하게 됩니다.

결론만 간단히 이야기 하자면, 커스텀 이벤트의 이름은 kebab-case를 사용해야 합니다.

2. 컴포넌트에서 v-model 사용하기

2.2.0 이상의 Vue 버전에서 추가된 기능입니다.

v-modelvalue 속성과 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의 값은 propschecked로 값이 전달 됩니다. 그리고 자식 컴포넌트에서 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 이름만 전달 되어야 합니다.

.syncv-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 }" 과 같이 사용할 수 없습니다. 파싱을 할 때 너무 많은 예외 케이스들이 발생하여 이와 같은 표현식은 사용할 수 없습니다.

참고

댓글
공지사항
최근에 올라온 글