본문 바로가기
front/vue

[vue3 공식문서 번역]Components.3.Events

by juniKang 2022. 4. 29.

이벤트 내보내기와 듣기 (Emiiting and Listening to Events)

컴포넌트는 커스텀 이벤트를 템플릿 표현식(v-on 핸들러 같은)으로 직접적으로 내보낼 수 있다. 내장된 $emit 함수를 사용해서:

<!-- MyComponent -->
<button @click="$emit('someEvent')">click me</button>

parent는 v-on을 사용해서 들을 수 있다:

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

.once 접근제어자는 컴포넌트 이벤트 리스너에도 지원된다:

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

컴포넌트와 props와 같이, 이벤트 이름은 자동적인 케이스 변형을 제공한다. camelCase로 이벤트를 내보내도, 부모에서 kebab-cased 리스너를 사용해서 들을 수 있다. props casing 처럼, 템플릿의 이벤트 리스너에는 kebab-case를 사용하는걸 추천한다.


네이티브 돔 이벤트와 다르게, 컴포넌트가 내보내는 이벤트들은 거품이 아니다. 직접적인 자식 컴포넌트에 의해 내보내진 이벤트만 들을 수 있다.

이벤트 아규먼트

이벤트와 특정한 값을 내보내는건 유용하다. 예를 들면, <BlogPost> 컴포넌트가 텍스트의 크기를 얼마만큼 키울지를 원할 수도 있다. 이 경우에, 값을 제공하기 위해 $emit에 부가적인  아규먼트로 넘길 수 있다:

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

부모에 있는 이벤트가 들을 때, 리스너로  이벤트 아규먼트에 접근하도록 해주는, 인라인 화살표 함수를 사용할 수 있다:

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

이벤트 핸들러가 메소드라면:

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

값은 메소드의 첫번째 파라미터로서 넘겨질 수 있다:

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


이벤트이름다음으로 $emit()에서 넘겨지는 모든 부가적인 아규먼트들은 리스너로 전달될것이다. 예를들어, $emit('foo', 1, 2, 3) 을 리스너 함수는 3개의 아규먼트를 전달 받을 것이다.

내보낸 이벤트 선언하기

내보낸 이벤트는 defineEmits() 매크로를 통해서 컴포넌트에 명시적으로 선언할 수 있다.

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

리턴된 emit 함수는 자바스크립트부분에서 이벤트를 내보낼 때 쓰인다.

 

<script setup>을 쓰지 않으면, 이벤트는 emits 옵션을 사용해서 선언될 수 있고, emit 함수는setup() 컨텍스트에서 내보내진다:

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

emit 옵션은 또한 객체형 문법을 지원한다. 객체형 문법은 내보내는 이벤트의 페이로드의 런타임 유효성을 수행할 수 있게 해준다:

<script setup>
const emit = defineEmits({
  submit(payload) {
    // 유효성의 성공여부를 가르키기 위해
    // true나 false를 리턴해야 함
  }
})
</script>

<script setup> 과 타입스크립트를 사용하면, 순수한 타입 애노테이션을 사용해서 내보내는 이벤트를 선언할 수 있다:

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

더 자세한건: Typing Component Emits

 

선택사항이지만, 컴포넌트가 어떻게 동작하는지 더 잘 설명하기 위해 내보내는 모든 이벤트를 정의하는 것을 추천한다. 이건 뷰가 fallthrough 어트리뷰트에서 알려진 리스너로 내보내도록 해준다.

 


만약 네이티브 이벤트(click같은)이 emits 옵션으로 정의되면, 리스너는 더이상, native 클릭 이벤트에는 응답하지 않고 컴포넌트가 내보낸 클릭 이벤트만 들을 것이다.

이벤트 유효성 검사

prop 타입 유효성검사와 유사하게, 내보내진 이벤트는 배열 문법 대신 객체 문법으로 정의되어 유효성검사 될 수도 있다.

 

유효성 검사에 더해서, 이벤트는 함수로 배정되는데, 그 함수는 이벤트가 유효한지 아닌지 가르키는 불린을 리턴하고 emit call을 넘기는 아규먼트를 받는다. 

<script setup>
const emit = defineEmits({
  // 유효성 검사를 안함
  click: null,
  
  // 유효성 submit 이벤트
  submit: ({ email, apssword }) => {
    if (email && password) {
      return true;
    } else {
      console.warn('Invalid submit event payload!')
      return false;
    }
  }
})

function submitForm(email, password) {
  emit('submit', { email, password })
}
</script>

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 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를 둘 다 가진 writable computed 프로퍼티를 사용하는 것이다. get메소드는 modelValue 프로퍼티를 리턴하고, set 메소드는 상응하는 이벤트를 내보내야 한다:

<!-- CustomInput.vue -->
<script setup>
import { computed } from 'vue'

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

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

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

v-model 아규먼트

기본적으로, 컴포넌트에있는 v-model은 update:modelValue를 이벤트로, modelValue를 prop으로 사용한다. 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>

예제보기

 

다양한 v-model 바인딩

 

By leveraging the ability to target a particular prop and event as we learned before with v-model arguments

이전에 배운 v-model 아규먼트와 같이 특정한 prop과 event를 대상으로 하는 기능을 레버리징하여, 여러개의 v-model 바인딩을 하나의 컴포넌트 인스턴스에 만들 수 있다.

 

각각의 v-model은 다른 prop에 동기화 된다. 컴포넌트에 추가적인 옵션의 추가 없이:

<UserName
  v-model:first-name="firstName"
  v-model:last-name="lastName"
/>
<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>

예제

v-model 접근지시어 다루기

폼 input 바인딩에 대해 배울 때, 내장된 접근지시어 .trim, .number, .lazy 를 봤을거다. 하지만 이런 경우에서, 커스텀한 접근지시어가 필요할 떄가 있을거다.

 

.capitalize라는 커스텀한 접근지시어를 만들어보자. capitalize는 v-model 바인딩에 의해 제공되는 문자열의 첫번째 글자를 대문자로 한다.

<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('ipdate:modelValue', $event.target.value)"
  />
</template>

컴포넌트의 modelModifiers prop은  capitalize를 담고 있으며, 값은 true입니다 - v-model 바인딩이 v-model.cpitalize="myText"이기 떄문이다.

 

이제 위에 prop set up을 가지고, 내보내는 값을 바꾸기위한 핸들러를 쓰고 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>

예제

아규먼트와 접근지시어 둘 다 v-model 바인딩을 위해, 생선된 prop의 이름이 arg + "접근지시어"이다. 예를들면:

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

이에 상응하는 선언은 이렇다:

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

console.log(props.titleModifiers) // { capitalize: true }

 

댓글