Vue 3.0/04.Component

[vue 3] 08. component에 적용된 v-model

  • -

이번 포스트에서는 자식 컴포넌트에 v-model을 적용하는 방법에 대해 알아보자.

 

v-model의 동작

 

native element에서의 v-model

native element 즉 html의 input 요소에 v-model을 적용하면 손쉽게 양방향의 바인딩이 구현된다. 그럼 사용자 정의 컴포넌트에서 처리하려면 어떤 과정이 필요할까?

사실 v-model은 내부적으로 value 속성에 값을 할당하는 v-bindinput 이벤트 시 값을 입력하는 v-on 두 개의 directive가 합쳐서 동작하는 것이다.

예를 들어 input 요소에 age를 연결하는 과정은 아래 두 가지 과정으로 나눠볼 수 있다.

<script setup>
import { ref } from 'vue';
 const age = ref(0);
</script>

<template>
  <label>vmodel : </label><input type="number" v-model="age">
  <label>사실은.. </label><input type="number" :value="age" @input="age = $event.target.value">
</template>

 

component에서의 v-model

그럼 위 개념이 그대로 component에 확장해 보자. 조상 컴포넌트(ex: input을 가지고 있는 녀석)에서 자식 컴포넌트(ex: input)에 값을 내려보내야 하고 자식 컴포넌트에서 변경된 값을 조상 컴포넌트에서 알아야 한다.

props와 event emit이 필요하다! v-model을 컴포넌트에 적용했을 때 사실은 다음과 같이 확장돼서 동작한다.

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

따라서 자식 컴포넌트에 v-model을 적용하기 위해서modelValue이라는 property와 update:<modelValue>라는 이벤트를 처리할 수 있어야 한다.

 

구현해 보기

먼저 자식 컴포넌트를 만들어보자.

<template>
  <div>
    <label>검색어: </label><br />
    <!--script에서 처리-->
    <input :value="modelValue" @input="handleInput" /><br>
    <!--inline 처리-->
    <input :value="modelValue" @input="$emit('update:modelValue', $event.target.value)" />
    </div>
</template>

<script setup>
defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])

const handleInput = ($event) => {
  console.log($event.target.value, '로 검색함')
  emit('update:modelValue', $event.target.value)
}
</script>

<style scoped></style>

 

이것을 사용하는 부모 컴포넌트에서는 props만 넘겨주면 되고 update:modelValue에 대한 처리는 자동으로 진행된다.

<script setup>
 import { ref } from 'vue';
 import CustomInput from './components/vmodel/CustomInput.vue';
 const age = ref(0);
 const searchText = ref("검색어");
</script>

<template>
  <label>vmodel : </label><input type="number" v-model="age">
  <label>사실은.. </label><input type="number" :value="age" @input="age = $event.target.value">

  <CustomInput v-model="searchText"></CustomInput>
  지금 검색어는: {{ searchText }}
</template>

<style scoped></style>

 

v-model argument 활용 (v-model:argument)

 

props 이름 변경

자식 컴포넌트에서 이미 modelValue라는 이름의 속성을 사용 중이라면 부모에서 받을 props의 이름을 변경해 주면 된다. 당연히 이벤트 이름도 그렇다. 

다음은 props 이름을 age, 이벤트 이름을 update:age로 변경한 예이다.

<script setup>
defineProps(['age'])
defineEmits(['update:age'])
</script>
<template>
  <input type="number" :value="age"  @input="$emit('update:age', $event.target.value)"/>
</template>

이제 부모 컴포넌트에서 이 컴포넌트를 사용하면서 v-model의 argument로 새롭게 설정한 age를 넘겨주면 된다. 이벤트는 특별히 설정할 필요 없다.

<CustomInput2 v-model:age="age"/>

 

여러 개의 v-model 처리

자식 컴포넌트에 여러 개의 v-model을 사용하려면 그냥 여러 개의 props와 emit을 선언해 주면 된다. 

<script setup>
defineProps(['age', 'searchText'])
defineEmits(['update:age', 'update:searchText'])
</script>
<template>
  <input type="number" :value="age" 
                      @input="$emit('update:age', $event.target.value)" />
  <input type="text" :value="searchText" 
                      @input="$emit('update:searchText', $event.target.value)" />
</template>

사용하는 쪽에서는 예약된 props를 사용해 주면 끝이다.

<MultipleVmodel v-model:age="age" v-model:search-text="searchText“/>

 

수식어 사용 처리

 

수식어 사용 처리

v-model에는 수식어(lazy, number, trim)를 이용해서 입력 값에 대한 부가적인 처리가 가능하다. 하지만 이들은 native input 요소에만 적용 가능할 뿐 컴포넌트에는 적용할 수 없다. 필요하다면 사용자 정의의 수식어를 만들어서 사용할 수 있다.

자식 컴포넌트에 입력되는 값의 첫 번째 글자를 대문자로 처리하는 수식어를 만든다고 생각해 보자. 그럼 부모 컴포넌트는 아래와 같이 작성될 것이다.

<label>첫글자 대문자</label>
<ModelModifier v-model:search-text.capitalize="searchText" />

그러면 자식 컴포넌트는 searchText 외에 searchTextModifiers라는 props가 추가로 필요하다. searchTextModifiers는 객체형태인데 전달되는 수식어가 true로 설정된다.(전달되지 않으면 false로 간주된다.)

const props = defineProps({
  searchText: String,
  searchTextModifiers: {},
})

이제 searchTextModifiers의 값을 이용해서 프로그래밍해 주면 된다.

<script>
const emit = defineEmits(['update:searchText'])
function emitSearchText(e) {
  let value = e.target.value
  if (props.searchTextModifiers.capitalize) {
    value = value.substring(0, 1).toUpperCase() + value.substring(1)
  }
  emit('update:searchText', value)
}
</script>
<template>
  <input type="text" :value="searchText" @change="emitSearchText" />
</template>

이제 부모 컴포넌트에서 두 가지 방식으로 자식 컴포넌트의 수식어를 사용해 보자.

// capitalize 수식어 전달
<ModelModifier v-model:search-text.capitalize="searchText" />
// 수식어 없음
<ModelModifier v-model:search-text="searchText" />

첫 번째 경우는 capitalize가 true로 전달되는 경우이고 두 번째는 전달되지 않으므로 false인 상황이어서 대문자화는 진행되지 않는다.

 

Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.