
Javascript에서 제공하는 기본적인 이벤트도 있지만 사용자가 특정 상황에 대한 이벤트를 만들 수도 있습니다. API를 호출하고 이를 약간 변형해서 처리 완료했을 때라거나 count를 세었는데 그게 완료되었다거나 하는 등 말이죠. 이러한 커스텀 이벤트를 어떻게 호출하고 컴포넌트에서는 어떻게 활용되는지 알아보겠습니다.
Emit과 Listen
이벤트에서 emit이란 트리거 같은 것이라 생각하시면 됩니다. 해당 이벤트가 발생했다고 알리는 것이죠. 반대로 listen은 이런 이벤트가 발생했는지 감지합니다.
컴포넌트에서 listen
태그에서 v-on
디렉티브를 사용하면 해당 태그에서 발생한 이벤트를 감지합니다. 컴포넌트도 마찬가지입니다.
<MyComponent @이벤트="콜백함수" />
보통은 여기서 이벤트가 click
, keyup
등 기본적인 것들이였지만 커스텀 이벤트들이 들어갈 수 있습니다.
<MyComponent @some-event="콜백함수" />
컴포넌트에서 emit
이 컴포넌트에서 커스텀 이벤트가 발생하려면 $emit
함수를 사용하면 됩니다.
export default {
methods: {
submit() {
this.$emit('someEvent')
}
}
}
컴포넌트 template 내에서도 동일하게 사용할 수 있습니다.
<template>
<button @click="$emit('someEvent')">click me</button>
</template>
props에서처럼 case 자동 변환을 해줍니다. 하지만 HTML 속성 쪽에서는 kebab-case, Javascript에서는 camelCase를 보통 사용하기 때문에 위에서처럼 사용하는 것을 추천합니다.
보통의 태그 이벤트와 다르게 버블링이 발생하지 않습니다. 해당 컴포넌트에서 emit한 이벤트만 listen할 수 있기 때문에 다른 컴포넌트나 더 하위 컴포넌트들끼리 이벤트를 주고 받으려면 Pinia와 같은 상태 관리 솔루션을 사용해야합니다.
Event 매개변수
이벤트를 emit했을 때 같이 전달해주고 싶은 값이 있을 수 있겠죠. 예를들어 1을 전달하고 싶다면 이번트명 다음에 차례로 넣어주면 됩니다.
<template>
<button @click="$emit('sum', 1, 2, 3)"></button>
</template>
그러면 부모 컴포넌트에서는 1, 2, 3 값이 전달될겁니다.
<template>
<ChildComponent @sum="sum" />
</template>
<script>
export default {
methods: {
sum(a, b, c) {
return a + b + c;
}
}
}
</script>
emit될 이벤트를 알리기
emits
값을 정의해서 현재 컴포넌트에서 어떤 이벤트를 emit할 지 명시할 수 있습니다.
export default {
emits: ['inFocus', 'submit']
}
배열로 전달하는 방법도 있지만, props처럼 객체로 전달해서 조건을 넣을 수도 있습니다.
export default {
emits: {
// 조건 없음
click: null,
// submit 이벤트 검사
submit: ({ email, password }) => {
if (email && password) {
// 성공
return true
} else {
console.warn('email 또는 password 값이 없습니다')
// 실패
return false
}
}
},
methods: {
submitForm(email, password) {
this.$emit('submit', { email, password })
}
}
}
물론 emits
값을 정하지 안해도 이벤트를 emit할 수 있습니다. 하지만 명시함으로써 Vue는 이벤트를 제한하고 사용자나 그 외 다른 라이브러리에 의해 발생할 수 있는 이벤트를 막음으로써 버그를 줄일 수 있습니다.
만약 emits을 정의했다면 기본 이벤트들도 모두 막히게 됩니다. click 이벤트가 필요하다면 emits에서 정의해주세요.
컴포넌트에서 v-model 사용하기
이전에 다룬 props와 emit을 조합하면 컴포넌트에서 v-model
을 사용할 수 있습니다. input에서의 v-model을 떠올려볼까요.
<input v-model="text" />
text 값이 바뀌면 input의 value 값이 바뀌고, 반대로 input의 value 값이 바뀌면 text값이 바뀌었습니다. 그리고 이는 v-on
과 v-bind
를 조합해서 사용한 것과 동일한 결과였습니다.
<input
:value="text"
@input="text = $event.target.value"
/>
컴포넌트에 v-model처럼 적용하려면 약간 다르게 해줘야합니다.
<CustomInput
:modelValue="text"
@update:modelValue="newValue => text = newValue"
/>
달라진 부분은 다음과 같습니다.
v-model을 사용하려면 modelValue
, update:modelValue
명칭을 사용해서 컴포넌트의 props와 emit을 이용하면 됩니다.
<script>
export default {
props: ["modelValue"],
};
</script>
<template>
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
</template>
input도 만약 v-model을 사용하고 싶다면 어떻게 해야할까요? props는 값을 쓸 수 없기 때문에 computed의 get, set을 이용하면 됩니다.
<script>
export default {
props: ['modelValue'],
computed: {
value: {
get() {
return this.modelValue
},
set(value) {
this.$emit('update:modelValue', value)
}
}
}
}
</script>
<template>
<input v-model="value" />
</template>
v-model 매개변수
modelValue 말고 다른 이름은 안되나요? 됩니다. v-model 뒤에 :이름
을 붙여주시면 됩니다.
<MyComponent v-model:title="bookTitle" />
title
이란 이름으로 바뀌었기 때문에 컴포넌트에서도 이에 맞게 바꿔줘야합니다.
<script>
export default {
props: ['title'],
emits: ['update:title']
}
</script>
<template>
<input
type="text"
:value="title"
@input="$emit('update:title', $event.target.value)"
/>
</template>
이렇게 이름을 바꿀 수 있기 때문에 동시에 여러 개로 바인딩도 가능합니다.
<UserName
v-model:first-name="first"
v-model:last-name="last"
/>
v-model 수식어
v-model에서 사용할 수 있는 수식어들이 있습니다.
trim
number
lazy
단순히 v-model 뒤에 .수식어
를 붙여줌으로써 간단하게 원하는 동작을 하도록 만들 수 있었습니다. 이 편한 기능을 컴포넌트에서 커스텀해서 저희가 원하는 수식어를 만들어보겠습니다.
예를 들어 입력한 알파벳 문자를 대문자로 바꿔주는 toUpper
라는 수식어를 만들어보고 싶다고 할께요.
<MyComponent v-model.toUpper="myText" />
to-upper가 아닌 toUpper인 이유는 컴포넌트에 전달될 때 대문자 변환을 해주지 않고 그대로 전달하기 때문입니다. 즉, to-upper로 사용하면 to-upper로 그대로 전달되서 script에서 사용하기에 불편해집니다.
v-model를 이용하면 modelValue를 props로 전달했듯이 modifier에 대한 값인 modelModifiers
도 전달됩니다.
<script>
export default {
props: {
modelValue: String,
// modelModifiers는 객체로 전달 받기 때문에
// default를 객체를 반환하는 함수(팩토리 함수)를 넣습니다
modelModifiers: {
default: () => ({})
// toUpper 포함된다면 { toUpper: true }
}
},
methods: {
emitValue() {}
}
}
</script>
<template>
<input type="text" :value="modelValue" @input="emitValue" />
</template>
이제 이 전달된 값을 대문자로 변환해줘야겠죠? 입력을 했을 때 대문자로 변환한 다음 emit해줍시다!
emitValue(e) {
this.$emit('update:modelValue', e.target.value.toUpperCase())
}
v-model 매개변수와 수식어
위에서 매개변수와 수식어에 대해 알아봤었는데요. 이를 같이 쓰게 되면 props와 emit의 명칭이 바뀌게 됩니다. 만약 title
로 v-model 값을 바꾸고 이를 대문자로 변환하는 수식어 toUpper
를 추가했다고 해보겠습니다.
<MyComponent v-model:title.toUpper="myText" />
컴포넌트에서는 title에 맞춰서 props와 emits을 바꿔주면 됩니다.
export default {
// modelValue -> title
// modelModifiers -> titleModifiers ({ toUpper: true })
props: ['title', 'titleModifiers'],
emits: ['update:title'],
}