# 커스텀 이벤트

이 페이지는 여러분이 이미 컴포넌트 기초를 읽었다고 가정하고 쓴 내용입니다. 컴포넌트가 처음이라면 기초 문서를 먼저 읽으시기 바랍니다.

# 이벤트 이름

컴포넌트 및 props와는 달리, 이벤트는 자동 대소문자 변환을 제공하지 않습니다. emit할 이벤트의 이름은 자동 대소문자 변환을 사용하는 것 대신 정확한 이름을 사용하여야 합니다. 예를 들어, 아래와 같이 camelCase로 작성된 이벤트를 emit하는 경우:

this.$emit('myEvent')
1

아래와 같이 kebab-case로 이벤트를 청취하는 경우 아무런 일도 일어나지 않습니다:

<!-- 작동안함 -->
<my-component @my-event="doSomething"></my-component>
1
2

컴포넌트 및 props와는 다르게 이벤트 이름은 자바스크립트 변수나 속성의 이름으로 사용되는 경우가 없으며, 따라서 camelCase나 PascalCase를 사용할 필요가 없습니다. 또한, (HTML이 대소문자를 구분하지 않는 특성 때문에) DOM 템플릿의 v-on 이벤트리스너는 항상 자동으로 소문자 변환되기 때문에 @myEvent 는 자동으로 @myevent로 변환됩니다. -- 즉, myEvent 이벤트를 들을 수 없습니다.

이러한 이유 때문에, 이벤트 이름에는  항상 kebab-case를 사용하는것이 권장됩니다.

# 커스텀 이벤트 정의하기

Vue School에서 커스텀 이벤트 정의에 대한 무료 영상보기

Emit된 이벤트는 컴포넌트 상에서 emits 옵션을 통해 정의될 수 있습니다.

app.component('custom-form', {
  emits: ['in-focus', 'submit']
})
1
2
3

만약 emits 옵션을 이용해 네이티브 이벤트(e.g., click)가 재정의 된 경우, 컴포넌트에 정의된 커스텀 이벤트가 네이티브 이벤트를 덮어씁니다.

TIP

컴포넌트가 어떻게 동작하는지를 좀더 잘 문서화하기 위해, 발생하는 모든 이벤트를 정의하는 것이 권장됩니다.

# Emit 된 이벤트 검사하기

prop 타입 검사와 비슷하게, emit된 이벤트 또한 배열 형식 대신 오브젝트 형식으로 선언함으로써 검사를 추가할 수 있습니다.

검사를 추가하기 위해서는 $emit의 전달인자를 받아 이벤트가 유효한지를 검증하여 boolean을 반환하는 함수를 이벤트에 할당합니다.

app.component('custom-form', {
  emits: {
    // 검사 절차 없음
    click: null,

    // submit 이벤트 검사
    submit: ({ email, password }) => {
      if (email && password) {
        return true
      } else {
        console.warn('잘못된 이벤트 페이로드입니다!')
        return false
      }
    }
  },
  methods: {
    submitForm() {
      this.$emit('submit', { email, password })
    }
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# v-model 인자

기본적으로 컴포넌트 상의 v-modelmodelValue를 props처럼, update:modelValue를 이벤트처럼 사용합니다.
이 때, v-model에 전달인자를 넘겨줌으로써 이 이름(modalValue)을 변경할 수 있습니다:

<my-component v-model:foo="bar"></my-component>
1

이 경우, 자식 컴포넌트는 foo를 prop으로, 동기화 이벤트에 대해서는 update:foo를 emit하도록 상정합니다:

const app = Vue.createApp({})

app.component('my-component', {
  props: {
    foo: String
  },
  template: `
    <input
      type="text"
      :value="foo"
      @input="$emit('update:foo', $event.target.value)">
  `
})
1
2
3
4
5
6
7
8
9
10
11
12
13
<my-component v-model:foo="bar"></my-component>
1

# 다중 v-model 바인딩

v-model arguments 단락에서 다루었던 개별 prop과 이벤트를 타겟하는 능력을 극대화하기 위해, 단일 컴포넌트 인스턴스에 대해 다중 v-model 바인딩을 만들 수 있습니다.

각 v-model은 추가 컴포넌트 옵션 없이도 다른 prop을 동기화합니다:

<user-name
  v-model:first-name="firstName"
  v-model:last-name="lastName"
></user-name>
1
2
3
4
const app = Vue.createApp({})

app.component('user-name', {
  props: {
    firstName: String,
    lastName: String
  },
  template: `
    <input
      type="text"
      :value="firstName"
      @input="$emit('update:firstName', $event.target.value)">

    <input
      type="text"
      :value="lastName"
      @input="$emit('update:lastName', $event.target.value)">
  `
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

See the Pen Multiple v-models by Vue (@Vue) on CodePen.

# v-model 수식어 핸들링

폼 인풋 바인딩에서 보았던 것 처럼, v-model
.trim, .number, .lazy 등의 빌트인 수식어 를 가지고 있습니다. 상황에 따라서 이러한 커스텀 수식어를 만들어 추가하는 것이 가능합니다.

예제로써, v-model 바인딩을 통해 전달된 문자열의 첫 글자를 대문자로 바꾸는 capitalize, 수식어를 만들어 봅시다.

컴포넌트의 v-model에 추가된 수식어는 modelModifiers prop을 통해 컴포넌트에 전달됩니다. 아래 예제에서는 빈 오브젝트를 기본값으로 설정하는 modelModifiers prop을 갖는 컴포넌트를 만들었습니다.

컴포넌트의 created 라이프사이클 훅이 호출되었을 때 --v-model 바인딩이 v-model.capitalize="bar"와 같이 선언되었기 때문에 -- modelModifiers prop이 capitalize를 포함하며 그 값이 true라는 것을 알아두세요.

<my-component v-model.capitalize="bar"></my-component>
1
app.component('my-component', {
  props: {
    modelValue: String,
    modelModifiers: {
      default: () => ({})
    }
  },
  template: `
    <input type="text"
      :value="modelValue"
      @input="$emit('update:modelValue', $event.target.value)">
  `,
  created() {
    console.log(this.modelModifiers) // { capitalize: true }
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

이제 우리의 prop이 셋업되었으며, modelModifiers 오브젝트의 키를 확인하고 emit된 값을 변경하기 위한 핸들러를 작성할 수 있습니다. 아래 예제는 <input /> 엘리먼트가 input 이벤트를 발생시켰을 때 문자열을 대문자로 바꿉니다.

<div id="app">
  <my-component v-model.capitalize="myText"></my-component>
  {{ myText }}
</div>
1
2
3
4
const app = Vue.createApp({
  data() {
    return {
      myText: ''
    }
  }
})

app.component('my-component', {
  props: {
    modelValue: String,
    modelModifiers: {
      default: () => ({})
    }
  },
  methods: {
    emitValue(e) {
      let value = e.target.value
      if (this.modelModifiers.capitalize) {
        value = value.charAt(0).toUpperCase() + value.slice(1)
      }
      this.$emit('update:modelValue', value)
    }
  },
  template: `<input
    type="text"
    :value="modelValue"
    @input="emitValue">`
})

app.mount('#app')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

v-model에 전달인자를 바인딩하기 위해서, prop의 이름을 arg + "Modifiers" 형태로 작성하여야 합니다:

<my-component v-model:foo.capitalize="bar"></my-component>
1
app.component('my-component', {
  props: ['foo', 'fooModifiers'],
  template: `
    <input type="text"
      :value="foo"
      @input="$emit('update:foo', $event.target.value)">
  `,
  created() {
    console.log(this.fooModifiers) // { capitalize: true }
  }
})
1
2
3
4
5
6
7
8
9
10
11

Deployed on Netlify.
Last updated: 12/20/2020, 2:33:30 PM