Vue3中的常见组件通信之v-model
概述
在vue3中常见的组件通信有props、mitt、v-model、 r e f s 、 refs、 refs、parent、provide、inject、pinia、slot等。不同的组件关系用不同的传递方式。常见的撘配形式如下表所示。
组件关系 | 传递方式 |
---|---|
父传子 | 1. props 2. v-model 3. $refs 4. 默认插槽、具名插槽 |
子传父 | 1. props 2. 自定义事件 3. v-model 4. $parent 5. 作用域插槽 |
祖传孙、孙传祖 | 1. $attrs 2. provide、inject |
兄弟间、任意组件间 | 1. mitt 2. pinia |
props和自定义事件详见本人另一篇文章:
Vue3中的常见组件通信之props和自定义事件
mitt用法详见本人另一篇文章:
Vue3中的常见组件通信之mitt
下面接着前面的文章继续记录v-model的用法。
4.v-model
v-model常用于普通html标签中的双向绑定,这个绑定用法无法实现跨组件通信,v-model用在组件标签中的时候,可以实现父子间的组件通信,而这样通信方式常用于UI组件库。要理解UI组件库的v-model的双向通信原理,需要先明白普通html标签中的v-model的底层原理。
4.1 普通HTML标签中v-model实现双向绑定的底层原理
在普通html标签中用v-model可以实现数据的双向绑定,如下代码所示是把input输入框里的数据与username进行双向绑定:
<template><div class="father"><h3>父组件</h3> <br><input id="input1" type="text" v-model="username"></div>
</template><script setup lang="ts" name="Father">import { ref } from "vue"; // 数据let username = ref('zhansan')</script><style scoped>
.father {height: 300px;padding: 20px;color: #ffffff; text-align:center; background-image: url(https://xyyhxxx.oss-cn-beijing.aliyuncs.com/picGoImg/202406041301030.png);background-size: cover
}
#input1{color: #000;
}
</style>
运行后在浏览器打开vue开发者工具,如下图所示:
上图中更改vue开发者工具中的username的值页面也会跟着发生变化,这个实现的是把数据呈现在页面,如果修改input输入框中的内容,username的数据也会跟着发生改变,这个实现的是页面传向数据,这就双向绑定中的双向,而实现这个双向绑定的关键就是在于input标签中写了v-model,如下所示:
<input id="input1" type="text" v-model="username">
而上面的代码实现的底层原理是这样的,先把上面的代码改成如下图所示:
<input id="input1" type="text" :value="username">
这样可以实现数据呈现在页面,数据修改页面也会跟着修改,但是修改页面,数据却不会变化,这只实现了一个方向的数据绑定,接着再给input标签增加属性,如下代码:
<input id="input1" type="text" :value="username" @input="username=$event.target.value">
这样再测试,就会发现页面中的数据也可以传向数据了,修改input标签中的内容,数据也会变化。
注意上面代码中@input="username= e v e n t . t a r g e t . v a l u e " 这句代码 t s 会报警,我们需要处理一下,对 event.target.value"这句代码ts会报警,我们需要处理一下,对 event.target.value"这句代码ts会报警,我们需要处理一下,对event.target进行断言,报警就会消失:
<input id="input1" type="text" :value="username" @input="username=(<HTMLInputElement>$event.target).value">
普通input标签中v-model实现双向绑定的底层原理就是:value+@input事件。
4.2组件标签中v-model中实现双向绑定
首先准备一个自己的UI组件,作为子组件,代码如下:
<template><input type="text">
</template><script setup lang="ts" name="MyInput"></script><style scoped>input{background-color:transparent;color: #ffffff;border: 0px;border-bottom: 1px solid #ffffff ;margin: 5px;}
</style>
然后在父组件中引入:
//引入MyInput组件import MyInput from "./MyInput.vue";
在父组件中把MyInput组件呈现在页面中:
<label>用户名:<MyInput/></label>
运行效果如下:
这样效果出来了,但是没有还没有实现数据绑定,首先在MyInput组件标签上增加:modelValue属性和绑定@update:model-value事件,如下代码:
<MyInput :modelValue="username" @update:model-value="username=$event"/>
然后需要在MyInput组件中声明props和声明事件来接收数据和事件:
//接收props
defineProps(["modelValue"])
//声明事件
let emit = defineEmits(['update:model-value'])
最后在MyInput组件中的普通html标签中添加:value属性和绑定@input事件:
<input type="text" :value="modelValue"@input="emit('update:model-value',(<HTMLInputElement>$event.target).value)"
>
至此,已经实现了父组件和子组件MyInput组件的双向通信,如下图所示:
最后在父组件中的MyInput组件标签上可以直接简写为如下代码:
<MyInput v-model="username"/>
实现的效果是完全一样的。
我们在用UI组件库的时候可以直接这样写,前提是UI组件库已经处理好了底层逻辑。
以下是完整代码:
父组件:
<template><div class="father"><h3>父组件</h3> <br><!-- <input id="input1" type="text" v-model="username"> --><!-- 下面是v-model 的本质 --><!-- <input id="input1" type="text" :value="username" @input="username=(<HTMLInputElement>$event.target).value"> --><!-- 下面是v-model 的本质 --><!-- <label>用户名:<MyInput :modelValue="username" @update:model-value="username=$event"/></label> --><label>用户名:<MyInput v-model="username"/></label></div>
</template><script setup lang="ts" name="Father">import { ref } from "vue"; //引入MyInput组件import MyInput from "./MyInput.vue";// 数据let username = ref('zhansan')</script><style scoped>
.father {height: 300px;padding: 20px;color: #ffffff; text-align:center; background-image: url(https://xyyhxxx.oss-cn-beijing.aliyuncs.com/picGoImg/202406041301030.png);background-size: cover
}
#input1{color: #000;
}
</style>
MyInput组件:
<template><input type="text" :value="modelValue"@input="emit('update:model-value',(<HTMLInputElement>$event.target).value)">
</template><script setup lang="ts" name="MyInput">//声明propsdefineProps(["modelValue"])//声明事件let emit = defineEmits(['update:model-value'])
</script><style scoped>input{background-color:transparent;color: #ffffff;border: 0px;border-bottom: 1px solid #ffffff ;margin: 5px;}
</style>
4.3一个UI组件实现多个数据传送
在父组件中可以改value,比如改成usName,如下代码所示:
<MyInput v-model:usName="username"/>
这个代码的本质是如下代码:
<MyInput :usName="username" @update:usName="username=$event"/>
在MyInput组件代码中就需要改成如下代码:
<template><input type="text" :value="usName"@input="emit('update:usName',(<HTMLInputElement>$event.target).value)">
</template><script setup lang="ts" name="MyInput">//声明propsdefineProps(["usName"])//声明事件let emit = defineEmits(['update:usName'])
</script>
这样改完后运行效果跟之前是完全一样的,接下来再扩展一下,父组件中的MyInput标签改成如下代码:
<MyInput v-model:usName="username" v-model:paword="password"/>
然后在MyInput组件中代码改成如下:
<template><input type="text" :value="usName"@input="emit('update:usName',(<HTMLInputElement>$event.target).value)"><br><input type="text" :value="paword"@input="emit('update:paword',(<HTMLInputElement>$event.target).value)">
</template><script setup lang="ts" name="MyInput">//声明propsdefineProps(["usName",'paword'])//声明事件let emit = defineEmits(['update:usName','update:paword'])
</script>
这样就实现一个组件内双向绑定两个数据了,如下图所示:
4.4小结
v-model可以实现父子间的通信,v-model即可以设置在普通html标签中,也可以设置在组件标签中,设置在组件标签中可以实现父子间的双向通信,前提是子组件底层做了处理。