vue
[vue3] defineModel 알아보기
weaklion
2025. 5. 28. 18:01
vue의 가장 큰 특징 중 하나인 양방향 데이터 바인딩에서 주로 사용되는 기능 중 하나가 v-model 입니다.
<script setup>
const model = ref('');
</script>
<template>
<input v-model="model" />
</template>
(아주)지난 포스팅에서 v-model의 대략적인 사용법에 대해 글을 적은 적이 있습니다. 개인적으로 vue의 가장 강력한 장점 중 하나라 생각하는데 최근에 나온 vue 3.4에서 이걸 더욱 편리하게 해주는 defineModel이 등장했습니다.
<script setup>
const model = defineModel()
</script>
<template>
<input v-model="model" />
</template>
뭔 차이야...? 생각 할 수 있는데 좀 더 봅시다.
InputField.vue
<script setup lang="ts">
const modelValue = defineModel<string>()
</script>
<template>
<input
type="text"
v-model="modelValue"
class="border px-2 py-1 rounded"
placeholder="입력하세요"
/>
</template>
App.vue
<script setup lang="ts">
import { ref } from 'vue'
import InputField from './components/InputField.vue'
const text = ref('초기값')
</script>
<template>
<div class="p-4">
<InputField v-model="text" />
<p class="mt-2 text-gray-600">입력된 값: {{ text }}</p>
</div>
</template>
코드를 작성하다 보면 공통 컴포넌트를 만들고 부모가 상태값을 제어하는 경우가 많습니다. vue에선 v-model을 통해 양방향 데이터 바인딩으로 값을 구현하죠.
부모에서 컴포넌트에 v-model에 제어할 상태값을 넣은 후 자식에서 그 값을 defineModel로 가져와 다시 v-model로 제어합니다. 이걸 defineModel 없이 써 봅시다.
<script setup lang="ts">
import { defineProps, defineEmits } from 'vue'
const props = defineProps<{
modelValue: string
}>()
const emit = defineEmits<{
(e: 'update:modelValue', value: string): void
}>()
</script>
<template>
<input
type="text"
:value="props.modelValue"
@input="event => emit('update:modelValue', event.target.value)"
class="border px-2 py-1 rounded"
placeholder="입력하세요"
/>
</template>
defineModel이 없을땐 수동으로 modelValue를 명시하고 emit으로 처리해야 합니다. 타입도 직접 명시해야 하며 코드량도 당연히 많아지겠죠.
input 하나만 해도 이정돈데, 더 많은 상태값을 다뤄야 한다면 그만큼 코드의 리팩토링이 힘들어질 수 있습니다.
defineModel을 썼을 때
UserForm.vue
<script setup lang="ts">
type UserInfo = {
name: string
email: string
}
const userInfo = defineModel<UserInfo>('userInfo')
const agreed = defineModel<boolean>('agreed', { required : true })
</script>
<template>
<div class="space-y-3">
<input
v-model="userInfo.name"
placeholder="이름"
class="border px-2 py-1 rounded w-full"
/>
<input
v-model="userInfo.email"
placeholder="이메일"
class="border px-2 py-1 rounded w-full"
/>
<label class="flex items-center gap-2">
<input type="checkbox" v-model="agreed" />
약관에 동의합니다
</label>
</div>
</template>
App.vue
<script setup lang="ts">
import { ref } from 'vue'
import UserForm from './components/UserForm.vue'
const userInfo = ref({ name: '홍길동', email: 'hong@example.com' })
const agreed = ref(false)
</script>
<template>
<UserForm v-model:userInfo="userInfo" v-model:agreed="agreed" />
<p>입력 결과: {{ userInfo }} / 동의 여부: {{ agreed }}</p>
</template>
defineModel 이전
<script setup lang="ts">
import { defineProps, defineEmits } from 'vue'
type UserInfo = {
name: string
email: string
}
const props = defineProps<{
userInfo: UserInfo
agreed: boolean
}>()
const emit = defineEmits<{
(e: 'update:userInfo', value: UserInfo): void
(e: 'update:agreed', value: boolean): void
}>()
// 반응형 객체로 만들기 위해 복사
const localUserInfo = { ...props.userInfo }
watchEffect(() => {
emit('update:userInfo', localUserInfo)
})
</script>
<template>
<div class="space-y-3">
<input
v-model="localUserInfo.name"
placeholder="이름"
class="border px-2 py-1 rounded w-full"
/>
<input
v-model="localUserInfo.email"
placeholder="이메일"
class="border px-2 py-1 rounded w-full"
/>
<label class="flex items-center gap-2">
<input
type="checkbox"
:checked="props.agreed"
@change="e => emit('update:agreed', e.target.checked)"
/>
약관에 동의합니다
</label>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import UserForm from './components/UserForm.vue'
const userInfo = ref({ name: '홍길동', email: 'hong@example.com' })
const agreed = ref(false)
</script>
<template>
<UserForm v-model:userInfo="userInfo" v-model:agreed="agreed" />
</template>
안 써도 되긴 하는데... 안 쓸 이유가 없겠죠?