Parent-child relation in Vue.js
In Vue.js, you have a One-Way Data Flow. We explore this on a couple of examples.
The simple input field
If you have an input field, you would usually need to pass an value and listen for input change to follow the One-Way Data Flow:
<input
:value="text"
@input="event => text = event.target.value">
To avoid overcomplicated code, there is v-model
<input v-model="text">
Component v-model
Now assume we have a custom component and we bind model value to it according to One-Way Data Flow:
<CustomInput
:modelValue="searchText"
@update:modelValue="newValue => searchText = newValue"
/>
The component has its own input field like this:
<!-- CustomInput.vue -->
<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>
<template>
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
</template>
For this case, we can bind searchText
like this on the CustomComponent:
<CustomInput v-model="searchText" />
Notice that we can't use
<input v-model="modelValue">
within CustomComponent, because modelValue
is a prop that should not be changed. However, we can use writable computed
property, and then we get this cleaner code:
<!-- CustomInput.vue -->
<script setup>
import { computed } from 'vue'
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
const value = computed({
get() {
return props.modelValue
},
set(value) {
emit('update:modelValue', value)
}
})
</script>
<template>
<input v-model="value" />
</template>
Component nested v-model
Assume we pass an object.
<script setup>
const user = ref({name: 'Max', age: '18'});
</script>
<template>
<CustomInput v-model="user" />
</template>
And inside CustomInput we have two inputs that wants to access name and age. We could do it like this:
<!-- CustomInput.vue -->
<script setup>
defineProps(['modelValue']);
defineEmits(['update:modelValue']);
const user = computed({
get() {
return props.modelValue
},
set(value){
emit('update:modelValue', value)
}
})
</script>
<template>
<input v-model="user.name" />
<input v-model="user.age" />
</template>