Vue 3 – 이벤트(emit)로 컴포넌트 간 데이터 전달하기

2025. 3. 30. 16:49Front-End/Vue.js

반응형

Vue에서 자식 컴포넌트가 부모에게 어떤 동작을 요청하거나 데이터를 전달해야 할 때 사용하는 방법이 있다.

바로 이벤트 발생(emit)이다.

이 글에서는 Vue 3 기준으로 emit을 이용한 컴포넌트 간 커뮤니케이션 방식과, v-model 커스텀 바인딩까지 정리한다.

 

기본 개념 – $emit

자식 컴포넌트에서 부모 컴포넌트로 이벤트를 전달하려면 $emit()을 사용한다.

<!-- ChildButton.vue -->
<template>
  <button @click="$emit('click-button')">클릭</button>
</template>
<!-- App.vue -->
<template>
  <ChildButton @click-button="handleClick" />
</template>

<script>
export default {
  methods: {
    handleClick() {
      console.log('버튼 클릭됨');
    },
  },
};
</script>

자식 컴포넌트에서 $emit('click-button')을 호출하면, 부모는 @click-button을 통해 이를 받을 수 있다.


이벤트에 데이터 담기

이벤트에는 원하는 값을 함께 보낼 수 있다.

<!-- MessageBox.vue -->
<template>
  <button @click="$emit('send-message', 'Hello', 'Vue')">메시지 전송</button>
</template>
<!-- App.vue -->
<template>
  <MessageBox @send-message="showMessage" />
</template>

<script>
export default {
  methods: {
    showMessage(a, b) {
      console.log(a, b); // "Hello", "Vue"
    },
  },
};
</script>
더보기

참고!

자식은 "이벤트 이름 + 전달할 값"만 정해서 보내고, 그걸 부모가 "받아서 처리하는 함수만 정의"하는 구조임

“나는 'send-message'라는 이름으로 'Hello'와 'Vue'를 보낼게. 이 이벤트가 발생하면 누군가(부모)가 처리하겠지!”


emits 옵션 사용

Vue 3에서는 어떤 이벤트가 발생할 수 있는지를 명시적으로 선언할 수 있다.

배열로 선언

export default {
  emits: ['send-message'],
  setup(_, { emit }) {     // props는 안쓰니깐 생략한다는 의미
    emit('send-message', '내용');
  },
};

객체로 선언 + 유효성 검사

export default {
  emits: {
    send: (payload) => {
      if (!payload) {
        console.warn('payload 누락됨');
        return false;
      }
      return true;
    },
  },
  setup(_, { emit }) {
    emit('send', '데이터');
  },
};

v-model 커스텀 컴포넌트 만들기

Vue 3에서는 컴포넌트에도 v-model을 사용할 수 있다. 일반적으로는 다음과 같은 형태로 작성한다.

<!-- App.vue -->
<template>
  <CustomInput v-model="username" />
</template>

<script>
export default {
  data() {
    return { username: '' };
  },
};
</script>

CustomInput.vue

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

<script>
export default {
  props: ['modelValue'],
  emits: ['update:modelValue'],
};
</script>

computed로 양방향 바인딩 처리

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

<script>
import { computed } from 'vue';

export default {
  props: ['modelValue'],
  emits: ['update:modelValue'],
  setup(props, { emit }) {
    const value = computed({
      get: () => props.modelValue,
      set: (val) => emit('update:modelValue', val),
    });

    return { value };
  },
};
</script>

v-model에 전달인자 사용하기

<!-- App.vue -->
<BookEditor v-model:title="title" />
<!-- BookEditor.vue -->
<template>
  <input :value="title" @input="$emit('update:title', $event.target.value)" />
</template>

<script>
export default {
  props: ['title'],
  emits: ['update:title'],
};
</script>

여러 개의 v-model 바인딩

<!-- App.vue -->
<BookEditor v-model:title="bookTitle" v-model:author="bookAuthor" />
<!-- BookEditor.vue -->
<template>
  <input :value="title" @input="$emit('update:title', $event.target.value)" />
  <input :value="author" @input="$emit('update:author', $event.target.value)" />
</template>

<script>
export default {
  props: ['title', 'author'],
  emits: ['update:title', 'update:author'],
};
</script>

v-model 수식어 처리

Vue는 v-model.capitalize="value"처럼 수식어를 붙일 수 있다. 이 경우, modelModifiers prop을 통해 수식어를 확인할 수 있다.

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

<script>
export default {
  props: {
    modelValue: String,
    modelModifiers: { default: () => ({}) },
  },
  emits: ['update:modelValue'],
  setup(props, { emit }) {
    const emitValue = (e) => {
      let val = e.target.value;
      if (props.modelModifiers.capitalize) {
        val = val.charAt(0).toUpperCase() + val.slice(1);
      }
      emit('update:modelValue', val);
    };

    return { emitValue };
  },
};
</script>

실전 예시 – 자식에서 부모로 "글자 키우기" 요청하기

<!-- App.vue -->
<template>
  <div :style="{ fontSize: fontSize + 'em' }">
    <ArticleCard @enlarge="fontSize += 0.2" />
  </div>
</template>

<script>
export default {
  data() {
    return { fontSize: 1 };
  },
};
</script>
<!-- ArticleCard.vue -->
<template>
  <article>
    <p>내용</p>
    <button @click="$emit('enlarge')">글자 키우기</button>
  </article>
</template>

<script>
export default {
  emits: ['enlarge'],
};
</script>

마무리

  • 자식 → 부모로 이벤트를 보낼 때는 $emit() 사용
  • Vue 3에서는 emits 옵션으로 발생 가능한 이벤트 정의
  • v-model은 modelValue와 update:modelValue로 구성
  • v-model:propName 형식으로 다중 바인딩 가능
  • modelModifiers를 통해 수식어 처리 가능

컴포넌트를 좀 더 유연하게 설계하려면 emit과 v-model 구조를 제대로 이해해두는 것이 중요하다. 프로젝트 규모가 커질수록 이런 기본 구조가 전체 흐름을 좌우한다.

반응형