Skip to content

Component v-model

با استفاده از v-model روی یک کامپوننت می‌توان یک اتصال دوطرفه ایجاد کرد.

در ابتدا بیایید نگاهی دوباره داشته باشیم که چطور v-model روی المان‌‌های بومی Html استفاده می‌شود:

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

در پشت پرده، کامپایلر تمپلیت، v-model را به معادل کامل‌تری برای ما تبدیل می‌کند. بنابراین کد بالا همان کار را انجام می‌دهد که کد زیر انجام می‌دهد:

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>
vue
<!-- CustomInput.vue -->
<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>

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

حالا v-model باید به خوبی با این کامپوننت کار کند:

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

یک راه دیگر برای پیاده‌سازی v-model درون این کامپوننت استفاده از یک پراپرتی computed قابل نوشتن با یک getter و یک setter است. تابع get باید مقدار پراپ modelValue را برگرداند و تابع set باید رویداد مربوط را منتشر کند:

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>

v-model آرگومان‌های

به طور پیش‌فرض، v-model روی یک کامپوننت از modelValue به عنوان پراپ و update:modelValue به عنوان رویداد استفاده می‌کند. می‌توانیم این نام‌ها را با افزودن یک آرگومان به v-model تغییر دهیم:

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

در این حالت، کامپوننت فرزند باید انتظار یک پراپ title را داشته باشد و با انتشار رویداد update:title مقدار والد را به‌روزرسانی کند:

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>

آزمایش این مورد در 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>

آزمایش این مورد در Playground

اتصال v-model چندگانه

با استفاده از توانایی هدف قراردادن یک پراپ و رویداد خاص که در آرگومان‌های v-model یاد گرفتیم، حالا می‌توانیم اتصال‌های v-model چندگانه، روی یک کامپوننت تکی ایجاد کنیم.

هر v-model بدون نیاز به گزینه‌های اضافه در کامپوننت با یک پراپ مختلف همگام‌سازی می‌شود:

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>

آزمایش این مورد در 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>

آزمایش این مورد در Playground

مدیریت پیراینده‌های v-model

وقتی درحال یادگیری اتصال‌های input در form‌ها بودیم، دیدیم که v-model دارای پیراینده‌های داخلی .trim، .number و .lazy بود. گاهی اوقات همچنین ممکن است بخواهید که v-model‌ای که روی کامپوننت سفارشی‌شده input خود قرار دادید هم قابلیت پشتیبانی از پیراینده‌های سفارشی را داشته باشد.

بیایید یک نمونه پیراینده سفارشی بسازیم، capitalize، که وظیفه داشته باشد حرف اول هر رشته‌ای که توسط اتصال v-model فراهم‌شده را به حالت بزرگ آن تبدیل کند:

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

پیراینده‌‌هایی که به v-model یک کامپوننت افزوده شده است، توسط پراپ modelModifiers به کامپوننت ارائه می‌شود. در مثال زیر، ما کامپوننتی ساختیم که دارای پراپ modelModifiers‌ای است که به طور پیش‌فرض با یک شی خالی مقداردهی شده است:

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>

توجه کنید که پراپ modelModifiers شامل capitalize است و مقدارش true تنظیم شده‌ چرا که در v-model متصل‌شده، تنظیم شده است.

حالا که پراپ خود را تنظیم کرده‌ایم، می‌توانیم کلیدهای شی modelModifiers را بررسی کنیم و یک handler برای تغییر مقدار منتشرشده بنویسیم. در قطعه کد زیر، ما حروف رشته را هر زمان که المان <input /> یک رویداد 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>

آزمایش این مورد در 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>

آزمایش این مورد در Playground

پیراینده‌ها برای v-model‌های آرگومان‌دار

برای اتصال‌های v-model‌ای که هم پیراینده و هم آرگومان دارند، نام پراپ تولیدشده به شکل arg + "Modifiers" خواهد بود. برای مثال:

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

تعاریف متناظر باید به این شکل باشند:

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 }
  }
}

اینجا مثال دیگری از استفاده از پیراینده‌ها با v-model چندگانه با آرگومان‌های مختلف را مشاهده می‌کنید:

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>
Component v-model has loaded