Two-Way Binding with defineModel in Vue 3.4
Before Vue 3.4, implementing two-way binding in custom components required manually defining the modelValue property and the update:modelValue event, and using computed to handle the binding. While functional, this approach was verbose and not very intuitive.
Here is the traditional method for implementing two-way binding in Vue versions prior to 3.4:
<!-- CustomInput.vue -->
<script setup lang="ts">
import { computed } from 'vue';
const props = defineProps<{
modelValue: string;
}>();
const emits = defineEmits<{
'update:modelValue': [value: string];
}>();
const value = computed({
get: () => props.modelValue,
set: (value: string) => emits('update:modelValue', value),
});
</script>
<template>
<input v-model="value" />
</template>
When using this component in a parent component:
<!-- App.vue -->
<script setup lang="ts">
import { ref } from 'vue';
import CustomInput from './components/CustomInput.vue';
const inputValue = ref<string>('');
</script>
<template>
<CustomInput v-model="inputValue" />
</template>
In this implementation, you need to define modelValue in defineProps, update:modelValue in defineEmits, and use computed to handle data binding. While functional, the process is quite complex.
The New Feature in Vue 3.4: defineModel
Vue 3.4 introduces a new function called defineModel, which significantly simplifies the implementation of two-way binding. Here’s how to use defineModel:
<!-- CustomInput.vue -->
<script setup lang="ts">
import { defineModel } from 'vue';
const val = defineModel<string>();
</script>
<template>
<input v-model="val" />
</template>
In the parent component:
<!-- App.vue -->
<script setup lang="ts">
import { ref } from 'vue';
import CustomInput from './components/CustomInput.vue';
const inputValue = ref<string>('');
</script>
<template>
<CustomInput v-model="inputValue" />
</template>
By using defineModel, there is no need to explicitly declare modelValue or update:modelValue. It directly supports two-way binding with v-model, simplifying the code and improving readability.
Advanced Usage
1. Setting Default Values
defineModel allows you to specify default values directly for model bindings:
const val = defineModel({ type: String, default: 'Default Value' });
If the parent component does not provide a binding value, the child component will use the default value.
2. Multiple Model Bindings
defineModel also supports defining multiple models for handling complex scenarios:
<!-- CustomInput.vue -->
<script setup lang="ts">
import { defineModel } from 'vue';
const name = defineModel<string>('name');
const address = defineModel<string>('address');
</script>
<template>
<input v-model="name" placeholder="Name" />
<input v-model="address" placeholder="Address" />
</template>
Usage in the parent component:
<!-- App.vue -->
<script setup lang="ts">
import { ref } from 'vue';
import CustomInput from './components/CustomInput.vue';
const nameInput = ref<string>('John Doe');
const addressInput = ref<string>('123 Main St');
</script>
<template>
<CustomInput v-model:name="nameInput" v-model:address="addressInput" />
</template>
This approach allows you to easily bind multiple data models, making the code more flexible.
Conclusion
defineModel is a significant update in Vue 3.4, making two-way binding in custom components simpler and more intuitive. By using defineModel, you can reduce the complexity of template code and easily implement multiple model bindings and default value settings. If your project can be upgraded to Vue 3.4 or later, it is highly recommended to replace the traditional modelValue implementation with defineModel.