组件v-model

lxf2023-05-05 01:02:19

v-model是vue里面非常重要的一个内置指令,作用是在表单输入元素或者组件上创建双向绑定,这些元素仅包含:

  • <input>
  • <select>
  • <textarea>
  • components

v-model其实就是value属性和input事件的语法

<input type="text" :value="iptVal" @input="$event => iptVal = $event.target.value" />
<!-- v-model -->
<input type="text" v-model="iptVal" />

我们在表单开发时通常会使用v-model指令来完成数据绑定,组件components上同样可以使用该指令。

基本使用

v-model 可以在组件上使用以实现双向绑定。上面的示例是v-mdoel在表单元素上的语法糖使用,而当在一个组件上使用时,它其实是modelValue属性和update:modelValue事件的语法糖

<IptCpn
  :modelValue="searchText"
  @update:modelValue="newValue => searchText = newValue"
/>
<!-- v-model -->
<IptCpn v-model="searchText" />

在组件内部需要做两件事来实现功能,参见官方文档的两句话:

  1. 将内部原生 <input> 元素的 value attribute 绑定到 modelValue prop
  2. 当原生的 input 事件触发时,触发一个携带了新值的 update:modelValue 自定义事件

所以子组件代码为:

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

这时组件v-model就可以工作了,但是子组件中的元素还是必须绑定value属性和监听input事件,如果子组件中的表单元素也想使用语法糖写法v-model来绑定状态,需要使用拥有getter 和 setter 的computed来编写:

<!-- IptCpn.vue -->
<script setup>
import { computed } from 'vue'
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
​
const modelValueComputed = computed({
  set(val) {
    emit('update:modelValue', val)
  },
  get() {
    return props.modelValue
  }
})
</script><template>
  <input
    type="text"
    v-model="modelValueComputed"
  />
</template>

修饰符

v-model 有一些内置的修饰符,例如 .trim.number.lazy。这些在组件v-model是同样可以使用的,并且支持自定义修饰符来扩展一些功能

<IptCpn v-model.trim="searchText" />

多个v-model

组件上的v-model可以使用多个,使用v-model传参(就是给v-model一个名字)

<!-- 父 -->
<IptCpn v-model="prop1" v-model:title="prop2"/><!-- IptCpn.vue -->
<script setup>
import { computed } from 'vue'
const props = defineProps(['modelValue', 'title'])
const emit = defineEmits(['update:modelValue', 'update:title'])
​
const modelValueComputed = computed({
  set(val) {
    emit('update:modelValue', val)
  },
  get() {
    return props.modelValue
  }
})
const titleComputed = computed({
  set(val) {
    emit('update:title', val)
  },
  get() {
    return props.title
  }
})
</script><template>
  <input
    type="text"
    v-model="modelValueComputed"
  />
  <input
    type="text"
    v-model="titleComputed"
  />
</template>

这样就可以在单个组件实例上创建多个 v-model 双向绑定,但是这种写法冗余代码太多,每增加一个v-model绑定就会多写一个computed,如果表单元素过多,那么代码量就会增加很多。

优化

我们来思考一下,子组件中的多个表单元素绑定的数据可以看成是一类状态,我们将这一类状态放进一个对象中再使用v-model绑定到组件中:

<IptCpn v-model="formData" />

这里有一个需要注意的地方,我们现在再输入框中输入值,改变的是对象里面的某个字段的值,而不是这个对象,所以不会进入到setter当中,我们可以使用Proxy来代理整个对象,当每个值发生更改时,都做一次emit

<script setup>
import { computed } from "vue"
const props = defineProps({
  modelValue: {
    type: Object,
    require: true,
  },
})
const emit = defineEmits(["update:modelValue"])
​
const modelValueComputed = computed({
  set(val) {
    // ❌
    emit("update:modelValue", val)
  },
  get() {
    //✅
    return new Proxy(props.modelValue, {
      set(target, name, value) {
        target[name] = value
        emit("update:modelValue", target)
        return true
      },
    })
  },
})
</script><template>
  <input type="text" placeholder="姓名" v-model="modelValueComputed.name" />
  <input type="text" placeholder="年龄" v-model="modelValueComputed.age" />
</template>

在实际开发中,还可以将该部分代码抽成一个hook,使其成为一个通用的方法,以便使用。

本网站是一个以CSS、JavaScript、Vue、HTML为核心的前端开发技术网站。我们致力于为广大前端开发者提供专业、全面、实用的前端开发知识和技术支持。 在本网站中,您可以学习到最新的前端开发技术,了解前端开发的最新趋势和最佳实践。我们提供丰富的教程和案例,让您可以快速掌握前端开发的核心技术和流程。 本网站还提供一系列实用的工具和插件,帮助您更加高效地进行前端开发工作。我们提供的工具和插件都经过精心设计和优化,可以帮助您节省时间和精力,提升开发效率。 除此之外,本网站还拥有一个活跃的社区,您可以在社区中与其他前端开发者交流技术、分享经验、解决问题。我们相信,社区的力量可以帮助您更好地成长和进步。 在本网站中,您可以找到您需要的一切前端开发资源,让您成为一名更加优秀的前端开发者。欢迎您加入我们的大家庭,一起探索前端开发的无限可能!