Porque Vue impone el flujo de datos unidireccional: las props van hacia abajo (padre a hijo), los eventos van hacia arriba (hijo a padre). Si un hijo modifica una prop directamente, el padre no se entera, y la próxima vez que el padre se re-renderice, sobreescribirá el cambio del hijo.
vue
<script setup lang="ts">
const props = defineProps<{ count: number }>()
function increment() {
props.count++ // ⚠️ Warning: Attempting to mutate prop "count"
}
</script>Vue muestra este warning porque casi siempre es un bug. El propietario de los datos (padre) y quien los muta (hijo) están desincronizados.
Cómo solucionarlo
Opción 1: Emitir un evento y dejar que el padre gestione el cambio.
vue
<!-- Hijo -->
<script setup lang="ts">
const props = defineProps<{ count: number }>()
const emit = defineEmits<{ update: [value: number] }>()
function increment() {
emit('update', props.count + 1)
}
</script>
<!-- Padre -->
<Counter :count="count" @update="count = $event" />Opción 2: Usar v-model (atajo para el patrón anterior).
vue
<!-- Hijo -->
<script setup lang="ts">
const count = defineModel<number>()
</script>
<template>
<button @click="count++">{{ count }}</button>
</template>
<!-- Padre -->
<Counter v-model="count" />Opción 3: Usar una copia local si la prop es solo un valor inicial.
ts
const props = defineProps<{ initialCount: number }>()
const count = ref(props.initialCount)Llama a la prop initialX para indicar que solo se usa una vez y no se mantendrá sincronizada con el padre.