<template> <CommonInput v-model="inputValue" /> </template> <script setup lang="ts"> import { ref } from "vue"; const inputValue = ref(); </script>子组件的代码如下:
<template> <input :value="props.modelValue" @input="emit('update:modelValue', $event.target.value)" /> </template> <script setup lang="ts"> const props = defineProps(["modelValue"]); const emit = defineEmits(["update:modelValue"]); </script>上面的例子大家应该很熟悉,以前都是这样去实现v-model双向绑定的。但是存在一个问题就是input输入框其实支持直接使用v-model的,我们这里却没有使用v-model而是在input输入框上面添加value属性和input事件。
<template> <CommonInput v-model="inputValue" /> </template> <script setup lang="ts"> import { ref } from "vue"; const inputValue = ref(); </script>子组件的代码如下:
<template> <input v-model="model" /> </template> <script setup lang="ts"> const model = defineModel(); model.value = "xxx"; </script>在上面的例子中我们直接将defineModel的返回值使用v-model绑定到input输入框上面,无需定义 modelValue 属性和监听 update:modelValue 事件,代码更加简洁。defineModel的返回值是一个ref,我们可以在子组件中修改model变量的值,并且父组件中的inputValue变量的值也会同步更新,这样就可以实现双向绑定。
<template> <input v-model="model" /> </template> <script setup lang="ts"> import { ref, watch } from "vue"; const props = defineProps(["modelValue"]); const emit = defineEmits(["update:modelValue"]); const model = ref(); watch( () => props.modelValue, () => { model.value = props.modelValue; } ); watch(model, () => { emit("update:modelValue", model.value); }); </script>看了上面的代码后你应该了解到了为什么可以在子组件内直接修改defineModel的返回值后父组件对应的变量也会同步更新了吧。我们修改的其实是defineModel返回的ref变量,而不是直接修改props中的modelValue。实现方式还是和vue3.4以前实现双向绑定一样的,只是defineModel这个宏帮我们将以前的那些繁琐的代码给封装到内部实现了。
const model = defineModel({ type: String, default: "20" });除了支持type和default,也支持required和validator,用法和定义prop时一样。
const model1 = defineModel("count1"); const model2 = defineModel("count2");在父组件中使用v-model时代码如下:
<CommonInput v-model:count1="inputValue1" /> <CommonInput v-model:count2="inputValue2" />我们也可以在多个v-model中定义type、default等
const model1 = defineModel("count1", { type: String, default: "aaa", });defineModel如何使用内置修饰符和自定义修饰符
<CommonInput v-model.trim="inputValue" />子组件也无需做任何修改,和上面其他的defineModel例子是一样的:
const model = defineModel();defineModel也支持自定义修饰符,比如我们要实现一个将输入框的字母全部变成大写的uppercase自定义修饰符,同时也需要使用内置的trim修饰符。
<CommonInput v-model.trim.uppercase="inputValue" />我们的子组件需要写成下面这样的:
<template> <input v-model="modelValue" /> </template> <script setup lang="ts"> const [modelValue, modelModifiers] = defineModel({ // get我们这里不需要 set(value) { if (modelModifiers.uppercase) { return value?.toUpperCase(); } }, }); </script>这时我们给defineModel传进去的第一个参数就是包含get 和 set 方法的对象,当对modelValue变量进行读操作时会走到get方法里面去,当对modelValue变量进行写操作时会走到set方法里面去。如果只需要对写操作进行拦截,那么可以不用写get。
{ trim: true, uppercase: true }在输入框进行输入时,就会走到set方法里面,然后调用value?.toUpperCase()就可以实现将输入的字母变成大写字母。
这篇文章介绍了如何使用defineModel宏实现双向绑定以及defineModel的实现原理。
1.在子组件内调用defineModel宏会返回一个ref对象,在子组件内可以直接对这个ref对象进行赋值,父组件内的相应变量也会同步修改。
2.defineModel其实就是在子组件内定义了一个ref变量和对应的prop,然后监听了对应的prop保持ref变量的值始终和对应的prop是一样的。
3.在子组件内当修改ref变量值时会抛出一个事件给父组件,让父组件更新对应的变量值,从而实现双向绑定。
4.使用defineModel({ type: String, default: "20" })就可以定义prop的type和default等选项。