목차
· v-model 속성
· v-model은 어떤 방식으로 동작할까?
· 무조건 v-model을 사용해도 되는걸까?
· v-model 문법을 사용하여 IME 입력을 처리하는 방법은?
· [부록] Emit과 Listen
· [부록] 컴포넌트에서 emit
v-model 속성
공식 문서에 안내된 v-model 속성의 사용법은 다음과 같다.
<input v-model="inputText"/>
이렇게 사용자의 입력을 받는 UI 요소들에 v-model 이라는 속성을 사용하면 입력 값이 자동으로 뷰 데이터 속성에 연결이 되어진다.
v-model은 어떤 방식으로 동작할까?
v-model 속성은 v-bind와 v-on의 기능 조합으로 동작한다. 매번 사용자가 일일히 v-bind와 v-on 속성을 지정해주지 않아도 좀 더 편하게 개발을 할 수 있게 고안된 문법이며, 앞에서 살펴본 코드를 아래와 같이 변경해도 동작한다.
<input v-bind:value="inputText" v-on:input="updateInput">
<script>
setup(){
const inputText = ref('');
const updateInput = (event) => {
const updatedText = event.target.value;
const inputText.value = updatedText;
};
}
</script>
위의 코드를 이해하기 위해서는 다음 내용을 필수로 인지하고 있어야 한다.
1. v-bind 속성은 뷰 인스턴스의 데이터 속성을 해당 HTML 요소에 연결할 때 사용한다.
2. v-on 속성은 해당 HTML 요소의 이벤트를 뷰 인스턴스의 로직과 연결할 때 사용한다.
3. 사용자의 이벤트에 의해 실행된 뷰 메서드(methods) 함수의 첫 번째 인자에는 해당 이벤트(event)가 들어온다.
HTML 입력 요소의 종류에 따라 `v-model` 속성은 다음과 같이 구성이 되어진다.
1. input 태그에는 `value / input`
2. checkbox 태그에는 `checked / change`
3. select 태그에는 `value / change`
무조건 v-model을 사용해도 되는걸까?
빠르게 기능을 구현하고, 프로덕트의 프로토타이핑을 해나갈 떄는 v-model을 사용해도 상관은 없다. 하지만, 현재 시점에서는 IME 입력(한국어, 일본어, 중국어)에 대하여 아래와 같은 문제점이 발생한다. 위 화면을 확인해보면, 한글 입력의 경우 한 글자에 대한 입력이 끝나야지만 데이터가 인풋 박스의 텍스트 값과 동기화가 된다. 위와 같은 v-model의 한계점 때문에 뷰 공식 문서에서는 한국어 입력을 다룰 때 value와 input의 디렉티브를 통한 직접 연결로 사용하는 것을 권고하고 있다.
v-model 문법을 사용하여 IME 입력을 처리하는 방법은?
이렇게 매번 한국어 입력을 처리할 때 v-model의 대체로 직접 이벤트와 값을 조합하여 바인딩 하는 것 자체가 귀찮게 느껴질 수 있다. 하지만 이럴 경우 인풋 컴포넌트를 별도의 컴포넌트로 분리하면 v-model로 편하게 처리를 할 수 있다.
<!-- Main.vue SFC 구조 -->
<template>
<BaseInput v-model:value="inputText"/>
<div> {{inputText}} </div>
</template>
<script>
import { ref } from 'vue';
import BaseInput from '../v-model/BaseInput.vue';
const inputText = ref("happy day-!");
</script>
해당 부분은 상위 컴포넌트에서 정의한 데이터 값을 하위 컴포넌트로 내려보내는 부분이다. 평소에 사용하던 props 속성 대신 v-model:value를 사용하였으며, 하위 컴포넌트에 value라는 프롭스 속성으로 내려간다는 사실을 추론할 수 있다.
<!-- BaseInput.vue SFC 구조 -->
<template>
<div>
<input v-bind:value="value" v-on:input="updateInput">
</div>
</template>
<script setup>
defineProps({
props: {
value: '',
};
});
const emit = defineEmits(['update:value']);
const updateInput = (event) => {
emit("update:value", event.target.value)
};
</script>
위 코드의 동작을 간단하게 설명을 하자면, 다음과 같다.
- BaseInput 컴포넌트의 상위 컴포넌트에서 props로 받은 value를 인풋 태그에 값으로 연결한다.
- 인풋 태그에서 값이 입력되면 인풋 태그에서 input 이벤트가 발생하고 updateInput 메서드가 실행된다.
- updateInput 메서드에서 인풋 태그에 입력된 값을 상위 컴포넌트에 update:value 이벤트로 올려 보낸다.
[부록] Emit과 Listen
이벤트에서 emit이란, 해당 이벤트가 발생했다고 알리는 트리거 같은 것이라고 생각하면 된다. 반대로 listen은 이러한 이벤트가 발생했는지 감지한다.
태그에서 v-on 디렉티브를 사용하면, 해당 태그에서 발생한 이벤트를 감지한다.
<MyComponent @이벤트="콜백함수"/>
보통은 여기서 이벤트가 click, keyup 등 기본적인 것들이었지만, 커스텀 이벤트가 들어갈 수 있다.
<MyComponent @some-event="콜백함수"/>
[부록] 컴포넌트에서 emit
이 컴포넌트에서 커스텀 이벤트가 발생하려면 $emit 함수를 사용하면 된다.
export default {
methods: {
submit() {
this.$emit('someEvent')
}
}
}
컴포넌트 template 내에서도 동일하게 사용이 가능하다.
<template>
<button @click="$emit('someEvent')"
</template>
props에서처럼 case 자동 변환을 해준다. 하지만 HTML 속성에서는 케밥 케이스, 자바스크립트에서는 카멜 케이스를 보통 사용하기 때문에 위에서처럼 사용하는 것을 추천한다. 그게 아니라면 케밥 케이스로 통일하여 사용을 하는 방법이 있다.
레퍼런스
댓글