Skip to content

v-model مع المكون

Basic Usage

v-model can be used on a component to implement a two-way binding.

Starting in Vue 3.4, the recommended approach to achieve this is using the defineModel() macro:

vue
<!-- Child.vue -->
<script setup>
const model = defineModel()

function update() {
  model.value++
}
</script>

<template>
  <div>Parent bound v-model is: {{ model }}</div>
  <button @click="update">Increment</button>
</template>

The parent can then bind a value with v-model:

template
<!-- Parent.vue -->
<Child v-model="countModel" />

The value returned by defineModel() is a ref. It can be accessed and mutated like any other ref, except that it acts as a two-way binding between a parent value and a local one:

  • Its .value is synced with the value bound by the parent v-model;
  • When it is mutated by the child, it causes the parent bound value to be updated as well.

This means you can also bind this ref to a native input element with v-model, making it straightforward to wrap native input elements while providing the same v-model usage:

vue
<script setup>
const model = defineModel()
</script>

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

Try it in the playground

Under the Hood

defineModel is a convenience macro. The compiler expands it to the following:

  • A prop named modelValue, which the local ref's value is synced with;
  • An event named update:modelValue, which is emitted when the local ref's value is mutated.

This is how you would implement the same child component shown above prior to 3.4:

vue
<!-- Child.vue -->
<script setup>
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
</script>

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

Then, v-model="foo" in the parent component will be compiled to:

template
<!-- Parent.vue -->
<Child
  :modelValue="foo"
  @update:modelValue="$event => (foo = $event)"
/>

As you can see, it is quite a bit more verbose. However, it is helpful to understand what is happening under the hood.

Because defineModel declares a prop, you can therefore declare the underlying prop's options by passing it to defineModel:

js
// making the v-model required
const model = defineModel({ required: true })

// providing a default value
const model = defineModel({ default: 0 })

WARNING

If you have a default value for defineModel prop and you don't provide any value for this prop from the parent component, it can cause a de-synchronization between parent and child components. In the example below, the parent's myRef is undefined, but the child's model is 1:

Child component:

js
const model = defineModel({ default: 1 })

Parent component:

js
const myRef = ref()
html
<Child v-model="myRef"></Child>

First let's revisit how v-model is used on a native element:

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

خلف الكواليس، يقوم مصرّف القوالب بتجزئة v-model إلى الحدث input@ و السمة value. لذلك، تقوم الشيفرة أعلاه بنفس الأمر الذي تقوم به الشيفرة الموالية:

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

عند استخدامه على مكون، يُجزّء v-model إلى ما يلي:

template
<CustomInput
  :model-value="searchText"
  @update:model-value="newValue => searchText = newValue"
/>

لكن ليشتغل هذا فعلا، يجب على <CustomInput> القيام بأمرين:

  1. اربط السمة value لعنصر <input> الأصلي مع الخاصية modelValue
  2. عندما تُشغل حدث input الأصلي ، ارسل حدث مخصص update:modelValue مع القيمة الجديدة

عمليا:

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

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

Now v-model should work perfectly with this component:

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

Try it in the Playground

Another way of implementing v-model within this component is to use a writable computed property with both a getter and a setter. The get method should return the modelValue property and the set method should emit the corresponding event:

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>

وسائط v-model

v-model on a component can also accept an argument:

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

In the child component, we can support the corresponding argument by passing a string to defineModel() as its first argument:

vue
<!-- MyComponent.vue -->
<script setup>
const title = defineModel('title')
</script>

<template>
  <input type="text" v-model="title" />
</template>

Try it in the Playground

If prop options are also needed, they should be passed after the model name:

js
const title = defineModel('title', { required: true })
Pre 3.4 Usage
vue
<!-- MyComponent.vue -->
<script setup>
defineProps({
  title: {
    required: true
  }
})
defineEmits(['update:title'])
</script>

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

اختبرها في حقل التجارب

In this case, instead of the default modelValue prop and update:modelValue event, the child component should expect a title prop and emit an update:title event to update the parent value:

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>

اختبرها في حقل التجارب

الربط المتعدد بـ v-model

باستخدام قدرة تحديد خاصية معينة وحدث معين كما تعلمنا من قبل مع وسائط v-model ، يمكننا الآن إنشاء عدة ارتباطات v-model على نسخة واحدة من المكون.

كل v-model سيتزامن مع خاصية مختلفة ، دون الحاجة إلى خيارات إضافية في المكون:

template
<UserName
  v-model:first-name="first"
  v-model:last-name="last"
/>
vue
<script setup>
const firstName = defineModel('firstName')
const lastName = defineModel('lastName')
</script>

<template>
  <input type="text" v-model="firstName" />
  <input type="text" v-model="lastName" />
</template>

Try it in the Playground

Pre 3.4 Usage
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>

اختبرها في حقل التجارب

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>

اختبرها في حقل التجارب

معالجة معدلات v-model

عندما كنا نتعلم عن ارتباطات إدخال النموذج، رأينا أن v-model لديه المعدلات المدمجة - .trim , .number و lazy.. في بعض الحالات، قد ترغب أيضًا في أن يدعم v-model على مكون إدخال مخصص بعض المعدلات المخصصة.

لنقم بإنشاء مثال لمعدل مخصص، capitalize، يقوم بتكبير الحرف الأول من السلسلةالنصية المقدمة من خلال ربط v-model:

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

Modifiers added to a component v-model can be accessed in the child component by destructuring the defineModel() return value like this:

vue
<script setup>
const [model, modifiers] = defineModel()

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

<template>
  <input type="text" v-model="model" />
</template>

To conditionally adjust how the value should be read / written based on modifiers, we can pass get and set options to defineModel(). These two options receive the value on get / set of the model ref and should return a transformed value. This is how we can use the set option to implement the capitalize modifier:

vue
<script setup>
const [model, modifiers] = defineModel({
  set(value) {
    if (modifiers.capitalize) {
      return value.charAt(0).toUpperCase() + value.slice(1)
    }
    return value
  }
})
</script>

<template>
  <input type="text" v-model="model" />
</template>

Try it in the Playground

Pre 3.4 Usage
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="props.modelValue" @input="emitValue" />
</template>

Try it in the Playground

Modifiers added to a component v-model will be provided to the component via the modelModifiers prop. In the below example, we have created a component that contains a modelModifiers prop that defaults to an empty object:

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>

Notice the component's modelModifiers prop contains capitalize and its value is true - due to it being set on the v-model binding v-model.capitalize="myText".

الآن، بعد أن قمنا بإعداد الخاصية، يمكننا التحقق من مفاتيح الكائن modelModifiers وكتابة مُعالِج لتغيير القيمة المرسلة. في الشيفرة أدناه، سنقوم بتكبير الحرف الأول من السلسلة النصية عندما يُطلق حدث input على عنصر </ input>.

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>

اختبرها في حقل التجارب

Modifiers for v-model with arguments

For v-model bindings with both argument and modifiers, the generated prop name will be arg + "Modifiers". For example:

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

التصريحات المقابلة يجب أن تكون:

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

Here's another example of using modifiers with multiple v-model with different arguments:

template
<UserName
  v-model:first-name.capitalize="first"
  v-model:last-name.uppercase="last"
/>
vue
<script setup>
const [firstName, firstNameModifiers] = defineModel('firstName')
const [lastName, lastNameModifiers] = defineModel('lastName')

console.log(firstNameModifiers) // { capitalize: true }
console.log(lastNameModifiers) // { uppercase: true }
</script>
Pre 3.4 Usage
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>
v-model مع المكون has loaded