Skip to content

컴포넌트 이벤트

이 페이지에서는 컴포넌트 기초를 이미 읽었다고 가정합니다. 컴포넌트를 처음 사용하는 경우, 그 문서를 먼저 읽으십시오.

이벤트 발신 및 수신하기

컴포넌트는 내장 메서드 $emit을 사용하여 템플릿 표현식(예: v-on 핸들러에서)에서 직접 사용자 정의 이벤트를 발신할 수 있습니다:

<!-- MyComponent -->
<button @click="$emit('someEvent')">클릭하기</button>

$emit() 메소드는 컴포넌트 인스턴스에서 this.$emit()로도 사용할 수 있습니다:

export default {
  methods: {
    submit() {
      this.$emit('submit')
    }
  }
}

그러면 부모는 v-on을 사용하여 수신 할 수 있습니다:

<MyComponent @some-event="callback" />

.once 수식어는 컴포넌트 이벤트 리스너에서도 지원됩니다.

<MyComponent @some-event.once="callback" />

컴포넌트 및 props와 마찬가지로 이벤트 이름은 자동 대소문자 변환을 제공합니다. 우리는 camelCase 형식으로 이벤트를 발신했지만, 부모에서 kebab-case 표기로 리스너를 사용하여 이를 수신할 수 있습니다. props 케이싱과 마찬가지로 템플릿에서 kebab-case 형식의 이벤트 리스너를 사용하는 것이 좋습니다.

TIP

네이티브 DOM 이벤트와 달리 컴포넌트 이벤트 발신은 버블링되지 않습니다. 직계 자식 컴포넌트에서 발생하는 이벤트만 수신할 수 있습니다.

이벤트 인자

이벤트와 함께 특정 값을 내보내는 것이 때때로 유용합니다. 예를 들어, <BlogPost> 컴포넌트가 텍스트를 얼마나 크게 확대할지 결정할 수 있습니다. 이러한 경우에는 $emit에 추가 인자를 전달하여 이 값을 제공할 수 있습니다:

<button @click="$emit('increaseBy', 1)">
  Increase by 1
</button>

그런 다음 부모가 이벤트를 수신할 때 인라인 화살표 함수를 리스너로 사용할 수 있습니다. 이를 통해 이벤트 인자에 접근할 수 있습니다:

<MyButton @increase-by="(n) => count += n" />

또는 이벤트 핸들러가 메서드인 경우:

<MyButton @increase-by="increaseCount" />

그러면 인자 값이 해당 메서드의 첫 번째 파라미터로 전달됩니다:

methods: {
  increaseCount(n) {
    this.count += n
  }
}
function increaseCount(n) {
  count.value += n
}

TIP

$emit()에서 이벤트 이름 뒤에 전달된 모든 추가 인자는 리스너로 전달됩니다. 예를 들어, $emit('foo', 1, 2, 3)을 사용하면 리스너 함수는 세 개의 인자를 받습니다.

발신되는 이벤트 선언하기

발신되는 이벤트는 defineEmits() 메크로를emits 옵션을 통해 컴포넌트에서 명시적으로 선언될 수 있습니다:

<script setup>
defineEmits(['inFocus', 'submit'])
</script>

<template>에서 사용한 $emit 메서드는 컴포넌트의 <script setup> 섹션 내에서 접근할 수 없지만, defineEmits()$emit 대신에 사용할 수 있는 동등한 함수를 반환합니다.

<script setup>
const emit = defineEmits(['inFocus', 'submit'])

function buttonClick() {
  emit('submit')
}
</script>

defineEmits() 매크로는 함수 내에서 사용할 수 없으므로, 위의 예제처럼 <script setup> 내에 직접 배치해야 합니다.

<script setup> 대신 명시적으로 setup 함수를 사용하는 경우, 이벤트는 emits 옵션을 사용하여 선언되어야 하며 emit 함수는 setup() 컨텍스트에 노출되어야 합니다:

export default {
  emits: ['inFocus', 'submit'],
  setup(props, ctx) {
    ctx.emit('submit')
  }
}

setup() 컨텍스트의 다른 속성과 마찬가지로 emit는 안전하게 분해할당할 수 있습니다:

export default {
  emits: ['inFocus', 'submit'],
  setup(props, { emit }) {
    emit('submit')
  }
}
export default {
  emits: ['inFocus', 'submit']
}

다른 setup() 컨텍스트의 속성들 처럼, emit 역시 구조분해가 가능합니다.

export default {
  emits: ['inFocus', 'submit'],
  setup(props, { emit }) {
    emit('submit')
  }
}

emits 옵션은 객체 구문도 지원하므로, 발신되는 이벤트의 전달 내용(payload)에 대한 런타임 유효성 검사를 수행할 수 있습니다:

<script setup>
const emit = defineEmits({
  submit(payload) {
    // `true` 또는 `false` 값을 반환하여
    // 유효성 검사 통과/실패 여부를 알려줌

    // 페이로드는 전달되는 인자를 나타내는 것으로
    // `emit('submit', 'a', 'b', 'c')`와 같이 3개의 인자를 전달하는 경우,
    // `submit(pl1, pl2, pl3) { /* 유효성 검사 반환 로직 */ }`과 같이
    // 유효성 검사에 페이로드를 사용할 수 있습니다.
  }
})
</script>

TypeScript를 <script setup>과 함께 사용하는 경우, 순수 타입스크립트 문법을 사용하여 발신되는 이벤트를 선언할 수도 있습니다:

<script setup lang="ts">
const emit = defineEmits<{
  (e: 'change', id: number): void
  (e: 'update', value: string): void
}>()
</script>

참고: 컴포넌트 Emits에 타입 지정하기

export default {
  emits: {
    submit(payload) {
      // `true` 또는 `false` 값을 반환하여
      // 유효성 검사 통과/실패 여부를 알려줌

      // 페이로드는 전달되는 인자를 나타내는 것으로
      // `emit('submit', 'a', 'b', 'c')`와 같이 3개의 인자를 전달하는 경우,
      // `submit(pl1, pl2, pl3) { /* 유효성 검사 반환 로직 */ }`과 같이
      // 유효성 검사에 페이로드를 사용할 수 있습니다.
    }
  }
}

참고: 컴포넌트 Emits에 타입 지정하기

선택 사항으로 컴포넌트가 작동하는 방식을 더 잘 문서화하기 위해 발신되는 모든 이벤트를 정의하는 것이 좋습니다. 또한 상위로부터 전달된 리스너는 폴스루 속성에 의해 제외할 수 있습니다.

TIP

네이티브 이벤트(예: click)가 emits 옵션에 정의된 경우 리스너는 이제 컴포넌트에서 발생하는 click 이벤트만 수신 대기하고 네이티브 click 이벤트에 더 이상 응답하지 않습니다.

이벤트 유효성 검사

props 타입 유효성 검사와 유사하게, 발신되는 이벤트는 배열 대신 객체 구문으로 정의된 경우 유효성을 검사할 수 있습니다.

유효성 검사를 추가하기 위해 이벤트에는 this.$emitemit 호출 시 전달되는 인자를 수신하고, 이벤트가 유효한지 여부를 나타내는 불리언 값을 반환하는 함수가 할당됩니다.

<script setup>
const emit = defineEmits({
  // 유효성 검사 없음
  click: null,

  // submit 이벤트 유효성 검사
  submit: ({ email, password }) => {
    if (email && password) {
      return true
    } else {
      console.warn('submit 이벤트 페이로드가 옳지 않음!')
      return false
    }
  }
})

function submitForm(email, password) {
  emit('submit', { email, password })
}
</script>
export default {
  emits: {
    // 유효성 검사 없음
    click: null,

    // submit 이벤트 유효성 검사
    submit: ({ email, password }) => {
      if (email && password) {
        return true
      } else {
        console.warn('submit 이벤트 페이로드가 옳지 않음!')
        return false
      }
    }
  },
  methods: {
    submitForm(email, password) {
      this.$emit('submit', { email, password })
    }
  }
}

v-model과 함께 사용하기

사용자 정의 이벤트는 v-model과 함께 작동하는 사용자 정의 입력을 생성하는 데 사용할 수도 있습니다. 다음 코드는:

<input v-model="searchText" />

다음과 동일합니다:

<input
  :value="searchText"
  @input="searchText = $event.target.value"
/>

컴포넌트에서 사용될 때 v-model은 대신 다음을 수행합니다:

<CustomInput
  :modelValue="searchText"
  @update:modelValue="newValue => searchText = newValue"
/>

이것이 실제로 작동하려면 컴포넌트 내부의 <input>이 다음을 충족해야 합니다:

  • value 속성을 modelValue prop에 바인딩

  • input에서 update:modelValue 이벤트로 새 값을 내보냅니다.

다음은 구현된 예제 입니다:

<!-- CustomInput.vue -->
<script>
export default {
  props: ['modelValue'],
  emits: ['update:modelValue']
}
</script>

<template>
  <input
    :value="modelValue"
    @input="$emit('update:modelValue', $event.target.value)"
  />
</template>
<!-- CustomInput.vue -->
<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>

<template>
  <input
    :value="modelValue"
    @input="$emit('update:modelValue', $event.target.value)"
  />
</template>

이제 v-model이 이 컴포넌트에서 완벽하게 작동해야 합니다:

<CustomInput v-model="searchText" />

이 컴포넌트 내에서 v-model을 구현하는 또 다른 방법은 getter와 setter 모두와 함께 쓰기 가능한 computed 속성을 사용하는 것입니다. get 메서드는 modelValue 속성을 반환해야 하고 set 메서드는 해당 이벤트를 내보내야 합니다:

<!-- CustomInput.vue -->
<script>
export default {
  props: ['modelValue'],
  emits: ['update:modelValue'],
  computed: {
    value: {
      get() {
        return this.modelValue
      },
      set(value) {
        this.$emit('update:modelValue', value)
      }
    }
  }
}
</script>

<template>
  <input v-model="value" />
</template>
<!-- CustomInput.vue -->
<script setup>
import { computed } from 'vue'

const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])

const value = computed({
  get() {
    return props.modelValue
  },
  set(value) {
    emit('update:modelValue', value)
  }
})
</script>

<template>
  <input v-model="value" />
</template>

v-model 인자

기본적으로 컴포넌트의 v-modelmodelValue를 prop으로 사용하고, update:modelValue를 이벤트로 사용합니다. v-model에 인자를 전달하여 이러한 이름을 수정할 수 있습니다.

<MyComponent v-model:title="bookTitle" />

이 경우 자식 컴포넌트는 title prop을 예상하고 update:title 이벤트를 내보내 상위 값을 업데이트해야 합니다:

<!-- MyComponent.vue -->
<script setup>
defineProps(['title'])
defineEmits(['update:title'])
</script>

<template>
  <input
    type="text"
    :value="title"
    @input="$emit('update:title', $event.target.value)"
  />
</template>

온라인 연습장으로 실행하기

<!-- MyComponent.vue -->
<script>
export default {
  props: ['title'],
  emits: ['update:title']
}
</script>

<template>
  <input
    type="text"
    :value="title"
    @input="$emit('update:title', $event.target.value)"
  />
</template>

온라인 연습장으로 실행하기

v-model 다중 바인딩

이전에 v-model 인자에서 배운 것처럼, 특정 prop과 이벤트를 대상으로 하는 기능을 활용하면 이제 단일 컴포넌트 인스턴스에 여러 v-model 바인딩을 만들 수 있습니다.

v-model은 컴포넌트에 추가 옵션이 필요 없이 다른 prop과 동기화됩니다:

<UserName
  v-model:first-name="first"
  v-model:last-name="last"
/>
<script setup>
defineProps({
  firstName: String,
  lastName: String
})

defineEmits(['update:firstName', 'update:lastName'])
</script>

<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)"
  />
</template>

온라인 연습장으로 실행하기

<script>
export default {
  props: {
    firstName: String,
    lastName: String
  },
  emits: ['update:firstName', 'update:lastName']
}
</script>

<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)"
  />
</template>

온라인 연습장으로 실행하기

v-model 수식어 핸들링

폼 입력 바인딩에 대해 배울 때, v-model.trim, .number.lazy와 같은 빌트인 수식어가 있었습니다. 그러나 경우에 따라 고유한 사용자 지정 수식어를 추가할 수도 있습니다.

v-model 바인딩으로 제공하는 문자열의 첫 글자를 대문자로 표시하는 사용자 지정 수식어 capitalize의 예를 만들어 보겠습니다:

<MyComponent v-model.capitalize="myText" />

컴포넌트 v-model에 추가된 수식어는 modelModifiers prop을 통해 컴포넌트에 제공됩니다. 아래 예제에서 우리는 기본적으로 빈 객체로 지정되는 modelModifiers prop을 포함하는 컴포넌트를 만들었습니다:




 




 










<script setup>
const props = defineProps({
  modelValue: String,
  modelModifiers: { default: () => ({}) }
})

defineEmits(['update:modelValue'])

console.log(props.modelModifiers) // { capitalize: true }
</script>

<template>
  <input
    type="text"
    :value="modelValue"
    @input="$emit('update:modelValue', $event.target.value)"
  />
</template>










 












<script>
export default {
  props: {
    modelValue: String,
    modelModifiers: {
      default: () => ({})
    }
  },
  emits: ['update:modelValue'],
  created() {
    console.log(this.modelModifiers) // { capitalize: true }
  }
}
</script>

<template>
  <input
    type="text"
    :value="modelValue"
    @input="$emit('update:modelValue', $event.target.value)"
  />
</template>

컴포넌트의 modelModifiers prop에는 capitalize가 포함되어 있고 그 값은 true인데, v-model 바인딩 시 v-model.capitalize="myText"에 의해서 설정되었기 때문입니다.

이제 modelModifiers 객체의 키를 확인하고 전달된 값을 변경하는 핸들러를 작성할 수 있습니다. 아래 코드에서는 <input /> 엘리먼트가 input 이벤트를 발생시킬 때마다 문자열을 대문자로 표시합니다.











 
 
 








<script setup>
const props = defineProps({
  modelValue: String,
  modelModifiers: { default: () => ({}) }
})

const emit = defineEmits(['update:modelValue'])

function emitValue(e) {
  let value = e.target.value
  if (props.modelModifiers.capitalize) {
    value = value.charAt(0).toUpperCase() + value.slice(1)
  }
  emit('update:modelValue', value)
}
</script>

<template>
  <input type="text" :value="modelValue" @input="emitValue" />
</template>

온라인 연습장으로 실행하기













 
 
 










<script>
export default {
  props: {
    modelValue: String,
    modelModifiers: {
      default: () => ({})
    }
  },
  emits: ['update:modelValue'],
  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)
    }
  }
}
</script>

<template>
  <input type="text" :value="modelValue" @input="emitValue" />
</template>

온라인 연습장으로 실행하기

인자와 수식어가 모두 있는 v-model 바인딩의 경우, 생성된 prop 이름은 인자이름 + "Modifiers"가 됩니다. 예를 들어:

<MyComponent v-model:title.capitalize="myText">

해당 선언은 다음과 같습니다:

const props = defineProps(['title', 'titleModifiers'])
defineEmits(['update:title'])

console.log(props.titleModifiers) // { capitalize: true }
export default {
  props: ['title', 'titleModifiers'],
  emits: ['update:title'],
  created() {
    console.log(this.titleModifiers) // { capitalize: true }
  }
}
컴포넌트 이벤트 has loaded