Passa al contenuto

Il v-model sui componenti

v-model può essere utilizzato su un componente per implementare un legame bidirezionale.

Prima di tutto, rivediamo come viene utilizzato v-model su un elemento nativo:

template
<input v-model="searchText" />

Nel backend, il compilatore del template espande v-model in un modo più verboso. Quindi, il codice sopra fa la stessa cosa del seguente:

template
<input
  :value="searchText"
  @input="searchText = $event.target.value"
/>

Quando usato su un componente, v-model invece si espande in questo modo:

template
<CustomInput
  :modelValue="searchText"
  @update:modelValue="newValue => searchText = newValue"
/>

Affinché ciò funzioni effettivamente, il componente <CustomInput> deve fare due cose:

  1. Collegare l'attributo value di un elemento <input> alla prop modelValue
  2. Quando scatta un evento nativo input emettere un evento personalizzato update:modelValue con il nuovo valore

Una dimostrazione pratica:

vue
<!-- CustomInput.vue -->
<script>
export default {
  props: ['modelValue'],
  emits: ['update:modelValue']
}
</script>

<template>
  <input
    :value="modelValue"
    @input="$emit('update:modelValue', $event.target.value)"
  />
</template>
vue
<!-- CustomInput.vue -->
<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>

<template>
  <input
    :value="modelValue"
    @input="$emit('update:modelValue', $event.target.value)"
  />
</template>

Ora il v-model dovrebbe funzionare perfettamente con questo componente:

template
<CustomInput v-model="searchText" />

Un altro modo per implementare v-model all'interno di questo componente è utilizzare una proprietà computed scrivibile con sia un getter che un setter. Il metodo get dovrebbe restituire la proprietà modelValue e il metodo set dovrebbe emettere l'evento corrispondente:

vue
<!-- CustomInput.vue -->
<script>
export default {
  props: ['modelValue'],
  emits: ['update:modelValue'],
  computed: {
    value: {
      get() {
        return this.modelValue
      },
      set(value) {
        this.$emit('update:modelValue', value)
      }
    }
  }
}
</script>

<template>
  <input v-model="value" />
</template>
vue
<!-- CustomInput.vue -->
<script setup>
import { computed } from 'vue'

const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])

const value = computed({
  get() {
    return props.modelValue
  },
  set(value) {
    emit('update:modelValue', value)
  }
})
</script>

<template>
  <input v-model="value" />
</template>

Argomenti v-model

Per impostazione predefinita, v-model su un componente utilizza modelValue come prop e update:modelValue come evento. Possiamo modificare questi nomi passando un argomento a v-model:

template
<MyComponent v-model:title="bookTitle" />

In questo caso, il componente figlio dovrebbe aspettarsi una prop title ed emettere un evento update:title per aggiornare il valore nel componente genitore:

vue
<!-- MyComponent.vue -->
<script setup>
defineProps(['title'])
defineEmits(['update:title'])
</script>

<template>
  <input
    type="text"
    :value="title"
    @input="$emit('update:title', $event.target.value)"
  />
</template>

Prova nel Playground

vue
<!-- MyComponent.vue -->
<script>
export default {
  props: ['title'],
  emits: ['update:title']
}
</script>

<template>
  <input
    type="text"
    :value="title"
    @input="$emit('update:title', $event.target.value)"
  />
</template>

Prova nel Playground

Molteplici v-model

Sfruttando la capacità di specificare una prop e un evento particolari come abbiamo imparato precedentemente con gli argomenti del v-model, ora possiamo creare più collegamenti v-model su una singola istanza del componente.

Ogni v-model si sincronizzerà con una prop diversa, senza bisogno di opzioni aggiuntive nel componente:

template
<UserName
  v-model:first-name="first"
  v-model:last-name="last"
/>
vue
<script setup>
defineProps({
  firstName: String,
  lastName: String
})

defineEmits(['update:firstName', 'update:lastName'])
</script>

<template>
  <input
    type="text"
    :value="firstName"
    @input="$emit('update:firstName', $event.target.value)"
  />
  <input
    type="text"
    :value="lastName"
    @input="$emit('update:lastName', $event.target.value)"
  />
</template>

Prova nel Playground

vue
<script>
export default {
  props: {
    firstName: String,
    lastName: String
  },
  emits: ['update:firstName', 'update:lastName']
}
</script>

<template>
  <input
    type="text"
    :value="firstName"
    @input="$emit('update:firstName', $event.target.value)"
  />
  <input
    type="text"
    :value="lastName"
    @input="$emit('update:lastName', $event.target.value)"
  />
</template>

Prova nel Playground

Gestire i modificatori del v-model

Quando abbiamo imparato a proposito dei vincoli per gli input nei form, abbiamo visto che v-model ha modificatori integrati - .trim, .number e .lazy. In alcuni casi, potresti anche voler supportare modificatori personalizzati per il v-model nel tuo componente input personalizzato.

Creiamo un esempio di modificatore personalizzato chiamato capitalize, che rende maiuscola la prima lettera della stringa fornita dal binding v-model:

template
<MyComponent v-model.capitalize="myText" />

I modificatori aggiunti a un componente v-model saranno forniti al componente tramite la prop modelModifiers. Nell'esempio qui sotto, abbiamo creato un componente che contiene una prop modelModifiers che per impostazione predefinita è un oggetto vuoto:

vue
<script setup>
const props = defineProps({
  modelValue: String,
  modelModifiers: { default: () => ({}) }
})

defineEmits(['update:modelValue'])

console.log(props.modelModifiers) // { capitalize: true }
</script>

<template>
  <input
    type="text"
    :value="modelValue"
    @input="$emit('update:modelValue', $event.target.value)"
  />
</template>
vue
<script>
export default {
  props: {
    modelValue: String,
    modelModifiers: {
      default: () => ({})
    }
  },
  emits: ['update:modelValue'],
  created() {
    console.log(this.modelModifiers) // { capitalize: true }
  }
}
</script>

<template>
  <input
    type="text"
    :value="modelValue"
    @input="$emit('update:modelValue', $event.target.value)"
  />
</template>

Nota che la prop modelModifiers del componente contiene capitalize e il suo valore è true - perché è stato impostato sul binding v-model v-model.capitalize="myText".

Ora che abbiamo la nostra prop impostata, possiamo controllare le chiavi dell'oggetto modelModifiers e scrivere un gestore per cambiare il valore emesso. Nel codice qui sotto, rendiamo maiuscola la stringa ogni volta che l'elemento <input /> scatena un evento input.

vue
<script setup>
const props = defineProps({
  modelValue: String,
  modelModifiers: { default: () => ({}) }
})

const emit = defineEmits(['update:modelValue'])

function emitValue(e) {
  let value = e.target.value
  if (props.modelModifiers.capitalize) {
    value = value.charAt(0).toUpperCase() + value.slice(1)
  }
  emit('update:modelValue', value)
}
</script>

<template>
  <input type="text" :value="modelValue" @input="emitValue" />
</template>

Prova nel Playground

vue
<script>
export default {
  props: {
    modelValue: String,
    modelModifiers: {
      default: () => ({})
    }
  },
  emits: ['update:modelValue'],
  methods: {
    emitValue(e) {
      let value = e.target.value
      if (this.modelModifiers.capitalize) {
        value = value.charAt(0).toUpperCase() + value.slice(1)
      }
      this.$emit('update:modelValue', value)
    }
  }
}
</script>

<template>
  <input type="text" :value="modelValue" @input="emitValue" />
</template>

Prova nel Playground

Modificatori per il v-model con argomenti

Per i binding v-model con sia un argomento che dei modificatori, il nome della prop generata sarà arg + "Modifiers". Ad esempio:

template
<MyComponent v-model:title.capitalize="myText">

Le dichiarazioni corrispondenti dovrebbero essere:

js
const props = defineProps(['title', 'titleModifiers'])
defineEmits(['update:title'])

console.log(props.titleModifiers) // { capitalize: true }
js
export default {
  props: ['title', 'titleModifiers'],
  emits: ['update:title'],
  created() {
    console.log(this.titleModifiers) // { capitalize: true }
  }
}

Ecco un altro esempio di utilizzo dei modificatori con più v-model con argomenti diversi:

template
<UserName
  v-model:first-name.capitalize="first"
  v-model:last-name.uppercase="last"
/>
vue
<script setup>
const props = defineProps({
  firstName: String,
  lastName: String,
  firstNameModifiers: { default: () => ({}) },
  lastNameModifiers: { default: () => ({}) }
})
defineEmits(['update:firstName', 'update:lastName'])

console.log(props.firstNameModifiers) // { capitalize: true }
console.log(props.lastNameModifiers) // { uppercase: true}
</script>
vue
<script>
export default {
  props: {
    firstName: String,
    lastName: String,
    firstNameModifiers: {
      default: () => ({})
    },
    lastNameModifiers: {
      default: () => ({})
    }
  },
  emits: ['update:firstName', 'update:lastName'],
  created() {
    console.log(this.firstNameModifiers) // { capitalize: true }
    console.log(this.lastNameModifiers) // { uppercase: true}
  }
}
</script>
Il v-model sui componenti has loaded