Skip to content

Component v-model

Sử dụng Cơ Bản

v-model có thể được sử dụng trên một thành phần để thực hiện một liên kết hai chiều.

Bắt đầu từ Vue 3.4, phương pháp khuyến nghị để đạt được điều này là sử dụng macro defineModel():

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

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

<template>
  <div>Giá trị v-model được liên kết từ phía cha là: {{ model }}</div>
</template>

Sau đó, phía cha có thể liên kết một giá trị với v-model:

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

Giá trị trả về bởi defineModel() là một ref. Nó có thể được truy cập và thay đổi giống như bất kỳ ref nào khác, ngoại trừ nó hoạt động như một liên kết hai chiều giữa một giá trị phụ thuộc vào cha và một giá trị cục bộ:

  • .value của nó được đồng bộ với giá trị được liên kết bởi v-model của phần tử cha;
  • Khi nó được thay đổi bởi thành phần con, nó làm cho giá trị được liên kết của cha được cập nhật theo.

Điều này có nghĩa là bạn cũng có thể liên kết ref này với một phần tử nhập người dùng native thông qua v-model, giúp bọc các phần tử nhập native một cách dễ dàng trong khi vẫn cung cấp cùng cách sử dụng v-model:

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

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

Ví dụ Playground

Bên Trong

defineModel là một macro tiện ích. Trình biên dịch mở rộng nó thành các phần sau:

  • Một prop có tên là modelValue, giá trị của ref cục bộ được đồng bộ với nó;
  • Một sự kiện có tên là update:modelValue, được phát ra khi giá trị của ref cục bộ thay đổi.

Đây là cách bạn sẽ triển khai cùng một thành phần con được hiển thị trước phiên bản 3.4:

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>

Như bạn có thể thấy, nó lớn hơn đáng kể. Tuy nhiên, hiểu được điều gì đang xảy ra phía dưới là hữu ích.

defineModel khai báo một prop, bạn có thể đặt ra các tùy chọn của prop cơ bản bằng cách chuyển nó vào defineModel:

js
// làm cho v-model là bắt buộc
const model = defineModel({ required: true })

// cung cấp một giá trị mặc định
const model = defineModel({ default: 0 })

Trước hết, hãy xem lại cách v-model được sử dụng trên một phần tử nguyên:

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

Bên dưới, trình biên dịch template mở rộng v-model thành đối tác dài dòng h

ơn cho chúng ta. Do đó, mã trên thực hiện cùng một công việc với đoạn mã sau:

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

Khi sử dụng trên một thành phần, v-model thay vào đó được mở rộng thành đoạn mã sau:

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

Tuy nhiên, để điều này hoạt động, thành phần <CustomInput> phải thực hiện hai điều sau:

  1. Ràng buộc thuộc tính value của một phần tử <input> nguyên bản với prop modelValue
  2. Khi sự kiện input nguyên tố được kích hoạt, phát ra sự kiện tùy chỉnh update:modelValue với giá trị mới

Dưới đây là ví dụ về điều đó:

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

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

Bây giờ v-model nên hoạt động hoàn hảo với thành phần này:

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

Thử nghiệm ở Playground

Một cách khác để triển khai v-model trong thành phần này là sử dụng một computed property có thể ghi được với cả getter và setter. Phương thức get nên trả về giá trị của modelValue và phương thức set nên phát ra sự kiện tương ứng:

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>

Đối Số của v-model

v-model trên một thành phần cũng có thể chấp nhận một đối số:

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

Trong thành phần con, chúng ta có thể hỗ trợ đối số tương ứng bằng cách truyền một chuỗi vào defineModel() như là đối số đầu tiên của nó:

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

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

Thử nghiệm ở Playground

Nếu cũng cần các tùy chọn của prop, chúng cũng nên được truyền sau tên của mô hình:

js
const title = defineModel('title', { required: true })
Sử dụng trước 3.4
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>

Thử nghiệm ở Playground

Trong trường hợp này, thay vì sử dụng prop mặc định modelValue và sự kiện update:modelValue, thành phần con nên mong đợi một prop title và phát ra sự kiện update:title để cập nhật giá trị của cha:

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>

Thử nghiệm ở Playground

Nhiều v-model Bindings

Bằng cách tận dụng khả năng định rõ một prop và sự kiện cụ thể như chúng ta đã học trước đó với đối số v-model, chúng ta có thể tạo nhiều v-model trên một thể hiện của thành phần duy nhất.

Mỗi v-model sẽ đồng bộ với một prop khác nhau, mà không cần sử dụng thêm tùy chọn trong thành phần:

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>

Thử nghiệm ở Playground

Sử dụng trước 3.4
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>

Thử nghiệm ở 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>

[Thử nghiệm ở Playground](https://play.vuejs.org/#eNqNkk1rg0AQhv/KIAETSJRexYYWeuqhl9JTt4clmSSC7i7rKCnif+/ObtYkELAiujPzztejQ/JqTNZ3mBRJ2e5sZWgrVNUYbQm+WrQfskE4WN1AmuXRwQmpUELh2Qv3eJBdTTAIBbDTLluhoraA4VpjXHNwL0kuV0EIYJE6q6IFcKhsSwWk7/qkUq/nq5be+aa5JztGfrmHu8t8GtoZhI2pJaGzAMrT03YYQk0YR3BnruSOZe5CXhKnC3X7TaP3WBc+ZaOc/1kk3hDJvYILRQGfQzx3Rct8GiJZJ7fA7gg/AmesNszMrUIXFpxbwCfZSh09D0Hc7tbN6sAWm4qZf6edcZgxrMHSdA3RF7PTn1l8lTIdhbXp1/CmhOeJRNHLupv4eIaXyItPdJEFD7R8NM0Ce/d/ZCTtESnzlVZXhP/vHbe

ZaT0tPdf59uONfx7mDVM=)

Xử lý Modifiers cho v-model

Khi chúng ta đang tìm hiểu về việc kết nối đầu vào của biểu mẫu, chúng ta thấy rằng v-modelmodifiers tích hợp sẵn - .trim, .number.lazy. Trong một số trường hợp, bạn cũng có thể muốn v-model trên thành phần input tùy chỉnh của bạn hỗ trợ các modifiers tùy chỉnh.

Hãy tạo một ví dụ về modifier tùy chỉnh, capitalize, chuyển đổi chữ cái đầu tiên của chuỗi được cung cấp bởi v-model:

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

Các modifiers được thêm vào v-model của một thành phần có thể được truy cập trong thành phần con bằng cách phân rã giá trị trả về của defineModel() như sau:

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

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

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

Để điều chỉnh điều kiện cách giá trị nên được đọc / ghi dựa trên các modifiers, chúng ta có thể truyền các tùy chọn getset cho defineModel(). Hai tùy chọn này nhận giá trị khi nhận / đặt giá trị của tham chiếu model và nên trả về giá trị đã được biến đổi. Dưới đây là cách chúng ta có thể sử dụng tùy chọn set để triển khai modifier capitalize:

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>

Thử nghiệm ở Playground

Sử dụng trước 3.4
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>

[Thử nghiệm ở Playground](https://play.vuejs.org/#eNp9Us1Og0AQfpUJF5ZYqV4JNTaNxyYmVi/igdCh3QR2N7tDIza8u7NLpdU0nmB+v5/ZY7Q0Jj10GGVR7iorDYFD6sxDoWRrtCU4gsUaBqitbiHm1ngqrfuV5j+Fik7ldH6R83u5GaBQlVaOoO03+Emw8BtFHCeFyucjKMNxQNiapiTkCGCzlw6kMh1BVRpJZSO/0AEe0Pa0l2oHve6AYdBmvj+/ZHO4bfUWm/Q8uSiiEb6IYM4A+XxCi2bRH9ZX3BgVGKuNYwFbrKXCZx+Jo0cPcG9l02EGL2SZ3mxKr/VW1hKty9hMniy7hjIQCSweQByHBIZCDWzGDwi20ps0Yjxx4MR73Jktc83OOPFHGKk7VZHUKkyFgsAEAqcG2Qif4WWYUml3yOp8wldlDSLISX+TvPDstAemLeGbVvvSLkncJSnpV2PQrkqHLOfmVHeNrFDcMz3w0iBQE1cUzMYBbuS2f55CPj4D6o0/I41

HzMKsP+u0kLOPoZWzkx1X7j18A8s0DEY=)

Các modifiers được thêm vào v-model của một thành phần sẽ được cung cấp cho thành phần qua prop modelModifiers. Trong ví dụ dưới đây, chúng ta đã tạo một thành phần chứa prop modelModifiers có giá trị mặc định là một đối tượng rỗng:

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>

Lưu ý rằng prop modelModifiers của thành phần chứa capitalize và giá trị của nó là true - do nó được đặt trên v-model binding v-model.capitalize="myText".

Bây giờ khi chúng ta đã thiết lập prop của mình, chúng ta có thể kiểm tra các key của đối tượng modelModifiers và viết một trình xử lý để thay đổi giá trị được phát ra. Trong đoạn mã dưới đây, chúng ta sẽ viết hoa chuỗi mỗi khi phần tử <input /> kích hoạt một sự kiện 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>

Thử nghiệm ở Playground

Modifiers cho v-model với tham số

Đối với v-model kết nối cả tham số và modifiers, tên prop được tạo ra sẽ là arg + "Modifiers". Ví dụ:

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

Các khai báo tương ứng sẽ là:

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

Dưới đây là một ví dụ khác về việc sử dụng modifiers với nhiều v-model với các tham số khác nhau:

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>
Sử dụng trước 3.4
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