log
Swift Code Chronicles

Two-Way Binding with defineModel in Vue 3.4

Published on January 4, 2025
Updated on January 4, 2025
19 min read
Vue

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.

About

A personal blog sharing technical insights, experiences and thoughts

Quick Links

Contact

  • Email: hushukang_blog@proton.me
  • GitHub

© 2025 Swift Code Chronicles. All rights reserved