深入了解v-model的原理,封装表单类组件并用v-model简化代码,.sync修饰符的介绍与使用

1.v-model的原理

v-model本质上是一个语法糖,

  • 在input文本框中,它是由value属性和oninput事件构成的
  • 在多选框中,它是由checked属性和onchange事件构成的
  • 在select下拉菜单中,它是由value属性和onchange事件构成的
1.1.验证:在input文本输入框中不使用v-model实现双向数据绑定
<!-- 1.验证v-model在input文本框中的拆分 -->
<input type="text" v-model="msg">
<p>输出msg的当前值:{{msg}}</p>
<!-- 此时v-model是一个语法糖,由value属性和oninput事件组成
其中,value是让数据向视图单向绑定,oninput是view向model单向绑定,
若要验证可以删掉其中一个看效果 -->
<input type="text" :value="msg" @input="msg=$event.target.value">     

其中,
1.数据变化,令视图跟着变化==>:value
2.视图变化,令数据跟着变化==>@input
$event.target.value表示输入框用户输入的值,
msg表示model层的数据

1.2.验证:v-model在下拉菜单中的拆分
<!-- 2.验证v-model在select下拉菜单中的拆分 -->
<!-- 使用v-model双向绑定: -->
<select name="" id="" v-model="cityId">
  <option value="101">北京</option>
  <option value="102">上海</option>
  <option value="103">广州</option>
  <option value="104">深圳</option>
</select>
<!-- 在下拉菜单中,v-model这个语法糖是由value属性和onchange事件构成的,验证如下:  -->
<select name="" id="" :value="cityId" @change="cityId=$event.target.value">
  <option value="101">北京</option>
  <option value="102">上海</option>
  <option value="103">广州</option>
  <option value="104">深圳</option>
</select>
2.表单类组件的封装
2.1.原理或步骤
  • 父传子:数据从父组件传过来,因为父组件要进行表单提交
    绑定数据:绑定的是v-model的拆解,因为不能让子组件直接修改父组件数据
  • 子传父:onchange事件监听用户对数据的修改,监听的结果传值给父组件,让父组件修改数据
父组件:(使用)
//<BaseSelect :cityId="selectId" @事件名="selectId=最新值"></BaseSelect>
<BaseSelect :cityId="selectId" @changeId="selectId=$event"></BaseSelect>
//父组件提供数据
data(){return{selectId:102}}
子组件:(封装)
注意这里不能用v-model,要把它拆解成value属性和onchange事件
<select :value="cityId" @change="handleChange">
    <option value="101">北京</option>
    ...
</select>
props:{
    cityId:String
},
methods:{
    handleChange(e){
        //console.log(e.target.value);//是否拿到104
        //this.$emit("事件名".e.target.value);
        this.$emit("changeId".e.target.value);
    }
    }
}
2.2.示例:表单类组件封装之下拉菜单select的封装

父组件:App.vue

//html
  <!-- $event是获得当前事件的形参 -->
  <base-v-model :cityId="selectId" @changeId="selectId=$event"></base-v-model>
  
//js
import BaseVModel from "./components/BaseVModel.vue"
export default {
	  components: {
	  	    BaseVModel,
	 },
	 data(){
	 	selectId:'102',//此处改成数数字然后子组件的props限制改成Number也可以
	 }
}

子组件:BaseVModel.vue

//html
<select name="" id="" :value="cityId" @change="handleChange">
  <option value="101">北京</option>
  <option value="102">上海</option>
  <option value="103">广州</option>
  <option value="104">深圳</option>
</select>
//js
props:{
  cityId:String//父传子:接收父组件传过来的数据
},
methods: {
  handleChange(e){
    console.log(e.target.value);//获取用户改变城市后对应的id:102,103...
    this.$emit("changeId",e.target.value);//子传父:把监听到的用户输入传给父组件
  },
}
3.使用v-model简化代码

使用v-model进行对上例代码简化,思路是把绑定的事件和属性凑成value+input的形式

  • 子组件:把:value=cityId替换为:value=value
  • 子组件:把this.$emit("changeId",e.target.value)替换成this.$emit("input",e.target.value)
  • 父组件:把:cityId:selectId替换成:value:selectId
  • 父组件:把@changeId="selectId=$event"替换成@input="selectId=$event"
  • 当父组件凑够:value@input后,可以使用v-model替换它们,即v-model="selectId"
完整代码

父组件:App.vue

//html
<!-- <base-v-model :value="selectId" @input="selectId=$event"></base-v-model> -->
<!-- 凑齐了:value和@input之后,就可以把它们改成v-model,从而达到了简化代码的目的 -->
<base-v-model v-model="selectId"></base-v-model>
//js
data:
	selectId:'102'

子组件:BaseVModel.vue

//html
<select name="" id="" :value="value" @change="handleChange">
  <option value="101">北京</option>
  <option value="102">上海</option>
  <option value="103">广州</option>
  <option value="104">深圳</option>
</select>
//js
props:{
 // cityId:String
 value:String//改成用value接收,input的拓展示例也用这个value属性
},
  methods: {
 handleChange(e){
   console.log(e.target.value);
   // this.$emit("changeId",e.target.value);
   this.$emit("input",e.target.value);
 },
4.拓展示例:完成input文本输入框的组件化封装

父组件:App.vue

//html
<!-- 拓展示例 -->
<base-v-model v-model="inputMsg"></base-v-model>
<p>input输入了什么:{{inputMsg}}</p>
//js
data(){
	inputMsg:"你好世界"
}

子组件:BaseVModel.vue

//html
<!-- 5.拓展和应用:把一个input文本框改成组件 -->
<input type="text" :value="value" @input="inputFunc">
//js
props:{
  // cityId:String
  value:String//改成用value接收,input的拓展示例也用这个value属性
},
methods: {
  inputFunc(e){
    this.$emit("input",e.target.value);
  }
},
5..sync修饰符
5.1.介绍

和v-model一样,.sync修饰符同样可以用于父组件和子组件的数据之间的双向绑定,区别在于
前者的props属性名固定为value,这就限制了它的使用场景,而后者的属性名不固定,更为灵活
.sync修饰符的本质是:属性名@update:属性名的合写

5.2.示例:实现弹框类子组件与父组件的双向绑定,实现父组件控制其显隐,要求props传递的属性名为visible

父组件:App.vue

//html
<base-dialog :visible.sync="visible"><base-dialog>
<button @click="visible=true">点击弹出窗口</button>

//js
data(){
	visible:false
}

子组件:BaseDialog.vue

<template>
  <div class="wrapper">
    <!-- step1:弹出框设置v-show并动态绑定visible -->
    <div class="modal-mask" v-show="visible">
      <div class="base-dialog">
        <div class="title">
          <h3>温馨提示</h3>
          <span class="close" @click="handleClick">&times;</span>
        </div>
        <div class="content">
          <p>你确认要退出本系统吗?</p>
        </div>
        <div class="footer">
          <button class="btn cancel">取消</button>
          <!-- step2:点击确认时,触发事件handleClick -->
          <button class="btn confirm" @click="handleClick">确认</button>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  // step3:props接收父组件传过来的属性
  props: {
    visible:Boolean//限制传递的属性为布尔类型
  },
  // step4:把监听到的用户行为(确认关闭)返回给父组件
  methods: {
    handleClick(){
      this.$emit("update:visible",false);//固定搭配--update:属性名
    }
  }
};
</script>
<style scoped>
/* 通过deekseek写的样式 */

/* 遮罩层样式 */
.modal-mask {
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  background: rgba(0, 0, 0, 0.5);
  z-index: 1000;
  display: flex;
  justify-content: center;
  align-items: center;
}

/* 弹窗容器 */
.base-dialog {
  background: #fff;
  border-radius: 8px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  width: 480px;
  max-width: 90vw;
  max-height: 80vh;
  display: flex;
  flex-direction: column;
}

/* 标题区域 */
.title {
  padding: 16px 24px;
  border-bottom: 1px solid #e8e8e8;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.title h3 {
  margin: 0;
  font-size: 18px;
  font-weight: 600;
  color: rgba(0, 0, 0, 0.85);
}

.close {
  cursor: pointer;
  font-size: 24px;
  color: #999;
  transition: color 0.3s;
}

.close:hover {
  color: #666;
}

/* 内容区域 */
.content {
  padding: 24px;
  flex: 1;
  overflow-y: auto;
  line-height: 1.6;
  color: rgba(0, 0, 0, 0.65);
}

/* 底部按钮区域 */
.footer {
  padding: 16px 24px;
  border-top: 1px solid #e8e8e8;
  text-align: right;
}

.btn {
  padding: 8px 20px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  margin-left: 12px;
  transition: all 0.3s;
}

.btn.confirm {
  background: #1890ff;
  color: white;
}

.btn.confirm:hover {
  background: #40a9ff;
  box-shadow: 0 2px 8px rgba(24, 144, 255, 0.3);
}

.btn.cancel {
  background: transparent;
  border: 1px solid #d9d9d9;
  color: rgba(0, 0, 0, 0.65);
}

.btn.cancel:hover {
  border-color: #1890ff;
  color: #1890ff;
}
</style>

效果:略

<think>我们参考了多个关于Vue2Vue3中v-model组件封装的引用,特别是引用[3]和引用[4]提供了详细的步骤和代码示例。在Vue3中,v-model的用法有所变化,支持多个v-model绑定,并且使用特定的prop和事件名。###Vue3中v-model组件封装的基本步骤1.**父组件通过v-model绑定数据**:在父组件中,使用`v-model:propName="data"`的形式将数据传递给子组件。其中`propName`是子组件接收的prop名称,`data`是父组件中的数据。2.**子组件声明prop**:在子组件中,使用`defineProps`(在`<scriptsetup>`中)或`props`选项(在选项式API中)来接收父组件传递的prop。3.**子组件触发更新事件**:当子组件需要修改该数据时,触发一个名为`update:propName`的事件,并将新值作为参数传递。4.**父组件接收更新**:由于v-model已经绑定了该事件,父组件中的数据会自动更新。###示例代码下面是一个简单的输入框组件封装示例,演示如何在Vue3中封装支持v-model组件。####子组件(MyInput.vue)```vue<template><div><labelv-if="label">{{label}}</label><input:type="type":value="modelValue"@input="$emit('update:modelValue',$event.target.value)"/></div></template><scriptsetup>defineProps({modelValue:[String,Number],//默认的v-model绑定使用的proplabel:String,type:{type:String,default:'text'}});defineEmits(['update:modelValue']);</script>```####父组件(ParentComponent.vue)```vue<template><div><h1>父组件数据:{{message}}</h1><MyInputv-model="message"label="用户名"/><!--也可以显式地指定prop和事件名,但使用默认的modelValue时可以简写为v-model--><!--简写形式等价于:v-model:modelValue="message"--></div></template><scriptsetup>import{ref}from'vue';importMyInputfrom'./MyInput.vue';constmessage=ref('');</script>```###多个v-model绑定在Vue3中,可以在一个组件使用多个v-model绑定,每个绑定对应不同的prop。例如:```vue<template><UserFormv-model:firstName="first"v-model:lastName="last"/></template>```在子组件中,我们需要分别定义`firstName`和`lastName`两个prop,并分别触发`update:firstName`和`update:lastName`事件。###总结-Vue3中,v-model默认使用`modelValue`作为prop,以`update:modelValue`作为事件。-可以通过`v-model:propName`的形式指定prop名称,实现多个v-model绑定。-组件通过定义相应的prop和触发对应的事件来实现双向绑定。[^3]:引用[3]提供了在组件中自定义v-model绑定prop名称的方法。[^4]:引用[4]详细介绍组件使用v-model的步骤。§§相关问题§§1.Vue2Vue3中v-model封装有什么区别?2.Vue3中,如何为同一个组件实现多个v-model绑定?3.Vue3的v-model中,默认的prop和事件名是什么?4.Vue3的`<scriptsetup>`中,如何定义和触发v-model相关的事件?</think> ### Vue3 v-model组件封装教程 在Vue3中封装支持v-model组件,主要涉及以下步骤(参考引用[3][4][5]): #### 基本封装步骤 1. **定义组件props** 在子组件使用`defineProps`接收父组件传递的值: ```vue <script setup> defineProps({ modelValue: { // 默认的v-model属性名 type: String, required: true } }) </script> ``` 2. **触发更新事件** 当子组件值变化时,通过`emit`触发更新事件: ```vue <template> <input :value="modelValue" @input="$emit('update:modelValue', $event.target.value)" /> </template> ``` #### 使用方式(父组件) ```vue <template> <!-- 默认v-model绑定 --> <CustomInput v-model="username" /> <!-- 自定义属性名绑定 --> <CustomInput v-model:title="bookTitle" /> </template> <script setup> import { ref } from 'vue' const username = ref('') const bookTitle = ref('Vue3指南') </script> ``` #### 完整组件示例(MyInput.vue) ```vue <template> <div class="input-wrapper"> <label v-if="label">{{ label }}</label> <input :type="type" :value="modelValue" @input="$emit('update:modelValue', $event.target.value)" :placeholder="placeholder" /> </div> </template> <script setup> defineProps({ modelValue: [String, Number], // 支持字符串和数字类型 label: String, type: { type: String, default: 'text' }, placeholder: String }) // 声明可触发的事件[^4] defineEmits(['update:modelValue']) </script> ``` #### 支持多个v-modelVue3中可以同时绑定多个v-model(引用[3][4]): ```vue <!--组件使用 --> <UserForm v-model:username="user.name" v-model:email="user.email" /> <!--组件内部 --> <script setup> defineProps(['username', 'email']) defineEmits(['update:username', 'update:email']) </script> ``` #### 关键注意事项 1. **默认属性名**:Vue3默认使用`modelValue`作为prop名(引用[3][5]) 2. **事件命名**:必须使用`update:propName`格式的自定义事件 3. **响应式更新**:父组件中绑定的值需使用`ref()`或`reactive()`创建 4. **`.sync`修饰符替代**:Vue2的`.sync`已被多个v-model替代(引用[2]) > **最佳实践**:对于表单组件,建议同时暴露`blur`、`focus`等事件以增强可定制性[^3][^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

前端OnTheRun

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值