组件 v-model

来自Wikioe
Eijux讨论 | 贡献2023年3月28日 (二) 20:42的版本
跳到导航 跳到搜索


关于

参考:Vue官方文档:《组件 v-model》

有点绕,做个笔记

v-model 用于组件

首先,需要理解 v-model 的等价形式(或者说展开形式)。

将 v-model 应用于组件时:

<CustomInput v-model="searchText" />
会被展开为:
<CustomInput
  :modelValue="searchText"
  @update:modelValue="newValue => searchText = newValue"
/>

👉相当于:将子组件的属性“modelValue”绑定到响应式状态 searchText,并监听事件“update:modelValue”以新值替换 searchText


不需要考虑背后是什么原理,只需要根据该形式,完成子组件即可

💡所以,子组件中需要:

  1. 声明“属性 modelValue”,并将 <input> 的“value 属性”绑定到该属性;
  2. 声明“事件 update:modelValue”,并让 <input> 的“input 事件”用“<input> 的新值”触发该事件。

子组件的实现(方式一

依据“展开形式”实现子组件,如下:

<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>

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

如上:

  1. 声明“属性 modelValue”和“事件 update:modelValue”;
  2. <input> 的“value 属性”绑定到“modelValue”;
  3. 监听 <input> 的“input 事件”,以携带“<input>的新值”(即,$event.target.value)触发“update:modelValue”。

子组件的实现(方式二

以“v-model”替换该“展开形式”实现子组件,如下:

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

如上:

  1. 声明“属性 modelValue”和“事件 update:modelValue”
  2. 提供计算属性“value”:
    1. 其 getter 用于:获取“modelValue”;
    2. 其 setter 用于:用 value 触发“update:modelValue”
  3. <input>双向绑定到“value”。

v-model:参数

v-model 的参数,用于指定v-model 绑定于“组件的哪个 prop”

——【是否使用参数,需要根据组件实现来定(组件的 <input> 的 value 绑定于哪个 prop)】
——【v-model 默认绑定于组件的“modelValue”属性,其对应的事件为“update:modelValue”】(如上节)

类似的:

<CustomInput v-model:pram="searchText" />

: 会被展开为:

<CustomInput
  :theSpecialProp="searchText"
  @update:theSpecialProp="newValue => searchText = newValue"
/>

多 v-model 绑定

对于“存在多个 <input> 的子组件”,只需要使用多个“带参数的 v-model”,以分别与子组件的 prop 绑定即可。

【两种写法,同上一节】

对于:

<UserName
  v-model:first-name="first"
  v-model:last-name="last"
/>

子组件的实现(方式一):

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

子组件的实现(方式二):

<script setup>
import { computed } from 'vue'

const props = defineProps({
  firstName: String,
  lastName: String
})

const emit = defineEmits(['update:firstName', 'update:lastName'])
  
const computedFirstName = computed({
  get() {
    return props.firstName
  },
  set(computedFirstName) {
    emit('update:firstName', computedFirstName)
  }
})

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

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

v-model:自定义修饰符

v-model 有一些内置的修饰符,例如 .trim.number.lazy。

但某些时候,需要 v-model 支持自定义的修饰符。

v-model 自定义修饰符:

  1. “自定义修饰符”可以通过在 defineProps 中定义 prop 来捕获。
    • 对于默认 prop“modelValue”,定义“修饰符 prop”为“modelModifiers”
    • 对于其他 prop,定义“修饰符 prop”为“prop名称 + Modifiers” —— 如:title 的“修饰符 prop”为 titleModifiers
  2. “自定义修饰符”的效果,可以套在 <input> 的事件处理外完成。

示例一:无参数的 v-model 修饰符

对于以下代码,通过 capitalize 修饰符使输入内容的首字母大写:

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

对应子组件的实现为:

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

示例二:有参数的 v-model 修饰符

对于以下代码,通过 capitalize 修饰符使输入内容的首字母大写:

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

对应子组件的实现为:

<script setup>
const props = defineProps({
  title: String,
  titleModifiers: { default: () => ({}) }
})

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

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

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

在“原生元素”上:

<input v-model="searchText" />

会被展开为:

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