当前位置: 首页 > article >正文

Vue v-model 深度解析:实现原理与高级用法

一、v-model 的本质

v-model 是 Vue 中最常用的指令之一,它本质上是一个语法糖,用于在表单元素和自定义组件上实现双向数据绑定。在 Vue 2.x 和 Vue 3.x 中,v-model 的实现机制有所不同,但核心思想都是简化数据绑定的过程。

1.1 表单元素上的 v-model

在原生表单元素上,v-model 会根据不同的输入类型自动扩展为不同的属性和事件:

<!-- 文本输入框 -->
<input v-model="message">
<!-- 等价于 -->
<input :value="message" @input="message = $event.target.value"><!-- 复选框 -->
<input type="checkbox" v-model="checked">
<!-- 等价于 -->
<input type="checkbox" :checked="checked" @change="checked = $event.target.checked"><!-- 单选按钮 -->
<input type="radio" v-model="picked" value="one">
<!-- 等价于 -->
<input type="radio" :checked="picked === 'one'" @change="picked = 'one'"><!-- 下拉选择框 -->
<select v-model="selected"><option value="a">A</option>
</select>
<!-- 等价于 -->
<select :value="selected" @change="selected = $event.target.value"><option value="a">A</option>
</select>

1.2 自定义组件上的 v-model

在自定义组件上,v-model 的实现原理有所不同:

Vue 2.x 实现方式

<CustomInput v-model="searchText" />
<!-- 等价于 -->
<CustomInput :value="searchText" @input="searchText = $event" 
/>

Vue 3.x 实现方式

<CustomInput v-model="searchText" />
<!-- 默认等价于 -->
<CustomInput :modelValue="searchText" @update:modelValue="searchText = $event" 
/>

二、v-model 的实现原理

2.1 Vue 2.x 的实现机制

在 Vue 2.x 中,v-model 的实现主要依赖于以下两个部分:

  1. 编译阶段:模板编译器会将 v-model 指令转换为 value prop 和 input 事件的组合
  2. 运行时:针对不同输入类型有特殊的处理逻辑

源码核心部分(简化):

// src/platforms/web/compiler/directives/model.js
function model(el, dir, _warn) {const value = dir.valueconst modifiers = dir.modifiersconst tag = el.tagconst type = el.attrsMap.typeif (tag === 'input' && type === 'checkbox') {// 处理复选框逻辑} else if (tag === 'input' && type === 'radio') {// 处理单选按钮逻辑} else if (tag === 'select') {// 处理下拉选择框逻辑} else if (tag === 'input' || tag === 'textarea') {// 处理文本输入逻辑} else if (!config.isReservedTag(tag)) {// 处理自定义组件逻辑genComponentModel(el, value, modifiers)}
}

2.2 Vue 3.x 的实现机制

Vue 3.x 对 v-model 进行了重构,使其更加灵活:

  1. 标准化处理:所有原生元素的 v-model 都统一为 modelValue 和 update:modelValue
  2. 多 v-model 支持:可以在一个组件上使用多个 v-model
  3. 自定义修饰符:可以更容易地创建自定义修饰符

源码核心部分(简化):

// packages/compiler-core/src/transforms/vModel.ts
export const transformModel: DirectiveTransform = (dir, node, context) => {const { exp, modifiers } = dirconst baseResult = baseTransform(dir, node, context)if (node.tagType === ElementTypes.COMPONENT) {// 处理组件逻辑return processComponentModel(node, exp, modifiers, context)} else {// 处理原生元素逻辑return processElementModel(node, exp, modifiers, context)}
}

三、自定义组件的 v-model 实现

3.1 Vue 2.x 中的实现

在 Vue 2.x 中,自定义组件要实现 v-model 需要:

  1. 接受一个 value prop
  2. 在需要时触发 input 事件

示例代码

Vue.component('CustomInput', {props: ['value'],template: `<input:value="value"@input="$emit('input', $event.target.value)">`
})

3.2 Vue 3.x 中的实现

Vue 3.x 中自定义组件的 v-model 实现:

  1. 默认使用 modelValue prop 和 update:modelValue 事件
  2. 可以自定义 prop 和事件名称

基本实现

app.component('CustomInput', {props: ['modelValue'],emits: ['update:modelValue'],template: `<input:value="modelValue"@input="$emit('update:modelValue', $event.target.value)">`
})

自定义参数

<CustomInput v-model:title="pageTitle" />
app.component('CustomInput', {props: ['title'],emits: ['update:title'],template: `<input:value="title"@input="$emit('update:title', $event.target.value)">`
})

四、v-model 修饰符

4.1 内置修饰符

  1. .lazy:将 input 事件改为 change 事件

    <input v-model.lazy="msg">
    
  2. .number:将输入值转为数字

    <input v-model.number="age" type="number">
    
  3. .trim:自动去除首尾空白字符

    <input v-model.trim="message">
    

4.2 自定义修饰符(Vue 3.x)

在 Vue 3.x 中,可以轻松实现自定义修饰符:

<CustomInput v-model.capitalize="text" />

组件实现:

app.component('CustomInput', {props: {modelValue: String,modelModifiers: {default: () => ({})}},emits: ['update:modelValue'],methods: {emitValue(e) {let value = e.target.valueif (this.modelModifiers.capitalize) {value = value.charAt(0).toUpperCase() + value.slice(1)}this.$emit('update:modelValue', value)}},template: `<input :value="modelValue" @input="emitValue">`
})

五、v-model 的高级用法

5.1 多个 v-model 绑定(Vue 3.x)

Vue 3.x 支持在单个组件上使用多个 v-model:

<UserNamev-model:first-name="firstName"v-model:last-name="lastName"
/>

组件实现:

app.component('UserName', {props: {firstName: String,lastName: String},emits: ['update:firstName', 'update:lastName'],template: `<input :value="firstName"@input="$emit('update:firstName', $event.target.value)"><input:value="lastName"@input="$emit('update:lastName', $event.target.value)">`
})

5.2 可复用的 v-model 逻辑

可以使用组合式 API 封装可复用的 v-model 逻辑:

// useModelWrapper.js
import { computed } from 'vue'export function useModelWrapper(props, emit, name = 'modelValue') {return computed({get: () => props[name],set: (value) => emit(`update:${name}`, value)})
}

组件中使用:

import { useModelWrapper } from './useModelWrapper'export default {props: ['modelValue'],emits: ['update:modelValue'],setup(props, { emit }) {const value = useModelWrapper(props, emit)return { value }}
}

六、v-model 的底层实现细节

6.1 编译过程分析

以 Vue 3.x 为例,v-model 的编译过程:

  1. 解析阶段:将模板解析为 AST
  2. 转换阶段:将 v-model 指令转换为对应的 props 和 events
  3. 代码生成:生成渲染函数代码

示例转换

<input v-model="message">

转换为:

_createVNode("input", {modelValue: message,"onUpdate:modelValue": $event => (message = $event)
})

6.2 运行时处理

在运行时,Vue 会处理这些生成的 props 和 events:

  1. 对于原生元素,Vue 会设置相应的 DOM 属性和事件监听器
  2. 对于组件,Vue 会将 props 传递给组件并监听相应的事件

核心处理逻辑

// packages/runtime-core/src/componentProps.ts
function setFullProps(instance,rawProps,props,attrs,isMounted = false
) {// 处理 modelValue 等 props
}// packages/runtime-core/src/componentEmits.ts
function emit(instance,event,...args
) {// 处理 update:modelValue 等事件
}

七、v-model 的性能考量

  1. 响应式更新优化

    • Vue 会对 v-model 绑定的值进行响应式跟踪
    • 只有值实际变化时才会触发更新
  2. 事件处理优化

    • 原生事件使用原生事件监听器
    • 组件事件使用 Vue 的事件系统
  3. 渲染优化

    • 在同一个 tick 中的多次更新会被合并
    • 使用虚拟 DOM diff 算法最小化 DOM 操作

八、常见问题与解决方案

8.1 自定义组件中 v-model 不工作

可能原因

  1. 没有正确声明 props
  2. 没有触发正确的事件
  3. Vue 版本不匹配(如 Vue 2.x 和 3.x 的差异)

解决方案

// Vue 2.x
props: ['value'],
methods: {updateValue(value) {this.$emit('input', value)}
}// Vue 3.x
props: ['modelValue'],
emits: ['update:modelValue'],
methods: {updateValue(value) {this.$emit('update:modelValue', value)}
}

8.2 修饰符不生效

可能原因

  1. 没有正确处理修饰符
  2. 自定义组件没有接收 modelModifiers

解决方案

props: {modelValue: String,modelModifiers: {default: () => ({})}
},
methods: {handleInput(e) {let value = e.target.valueif (this.modelModifiers.capitalize) {value = value.charAt(0).toUpperCase() + value.slice(1)}this.$emit('update:modelValue', value)}
}

九、v-model 的最佳实践

  1. 命名约定

    • 对于基础表单组件,使用默认的 modelValue
    • 对于有明确含义的组件,使用自定义参数名(如 v-model:value)
  2. 复杂数据处理

    • 对于复杂数据,考虑使用计算属性作为中间层
    • 可以使用自定义修饰符处理特殊格式要求
  3. 表单验证集成

    • 将 v-model 与表单验证库(如 VeeValidate)结合使用
    • 在自定义组件中封装验证逻辑
  4. 性能优化

    • 对于大型表单,考虑使用 .lazy 修饰符减少触发频率
    • 避免在 v-model 绑定的值中进行复杂计算

十、总结

v-model 作为 Vue 的核心特性之一,提供了简洁高效的双向数据绑定机制。通过深入了解其实现原理和底层机制,开发者可以:

  1. 更灵活地在项目中应用 v-model
  2. 创建更符合业务需求的自定义表单组件
  3. 避免常见的陷阱和性能问题
  4. 充分利用 Vue 2.x 和 3.x 中的新特性

随着 Vue 生态的发展,v-model 的功能也在不断扩展,掌握其核心原理将帮助开发者更好地适应未来的变化。

相关文章:

Vue v-model 深度解析:实现原理与高级用法

一、v-model 的本质 v-model 是 Vue 中最常用的指令之一&#xff0c;它本质上是一个语法糖&#xff0c;用于在表单元素和自定义组件上实现双向数据绑定。在 Vue 2.x 和 Vue 3.x 中&#xff0c;v-model 的实现机制有所不同&#xff0c;但核心思想都是简化数据绑定的过程。 1.1…...

STM32F103单片机在不需要使用 JTAG 调试接口的情况下,释放引脚给其他功能使用。

最近调试STM32F103的时候&#xff0c;由于引脚比较紧张就用了PB3(SYS_JTDO-TRACESWO)引脚&#xff0c;带电下载完程序后&#xff0c;功能都是正常运行&#xff0c;但是断电再上电&#xff0c;PB3引脚就不受控制了&#xff0c;后来查了一下发现PB3不是普通的IO&#xff0c;需要关…...

手机浏览器IP归属地查询全指南:方法与常见问题解答

在当今数字化时代&#xff0c;手机浏览器已成为人们日常生活中不可或缺的工具之一。然而&#xff0c;在使用手机浏览器的过程中&#xff0c;有时我们需要了解当前网络连接的IP归属地信息&#xff0c;那么&#xff0c;手机浏览器IP归属地怎么查看呢&#xff1f;本文将详细介绍几…...

Microsoft Azure DevOps针对Angular项目创建build版本的yaml

Azure DevOps针对Angular项目创建build版本的yaml&#xff0c;并通过变量控制相应job的执行与否。 注意事项&#xff1a;代码前面的空格是通过Tab控制的而不是通过Space控制的。 yaml文件中包含一下内容&#xff1a; 1. 自动触发build 通过指定code branch使提交到此代码库的…...

Web 架构之负载均衡全解析

文章目录 一、引言二、思维导图三、负载均衡的定义与作用定义作用1. 提高可用性2. 增强性能3. 实现扩展性 四、负载均衡类型硬件负载均衡代表设备优缺点 软件负载均衡应用层负载均衡代表软件优缺点 网络层负载均衡代表软件优缺点 五、负载均衡算法轮询算法&#xff08;Round Ro…...

Linux系统管理与编程16:PXE自动化安装部署centos7.9操作系统

兰生幽谷&#xff0c;不为莫服而不芳&#xff1b; 君子行义&#xff0c;不为莫知而止休。 0.准备 1&#xff09;防火墙和SELinux systemctl stop firewalld systemctl disable firewalld setenforce 0 sed -i s/^SELINUX.*/SELINUXdisabled/ /etc/selinux/config (很不好的…...

金丝雀/灰度/蓝绿发布的详解

以下是 金丝雀发布、灰度发布 和 蓝绿发布 的详细解析&#xff0c;涵盖核心原理、技术实现、适用场景及实际案例&#xff1a; 1. 金丝雀发布 (Canary Release) 核心原理 渐进式流量切换&#xff1a;将新版本部署到生产环境后&#xff0c;逐步将用户流量从旧版本迁移到新版本&…...

如何通过ABAP获取SAP生产订单的目标成本

SAP存储生产订单成本的主要底表包括&#xff1a; COBK: CO凭证表头COEP: CO凭证行项目COSS: 来自CO内部的汇总数据COSP: 来自CO外部部的汇总数据 先说结论&#xff1a;SAP 对生产订单的目标成本是没有保存到底表的。那么如何通过代码的方式获取呢&#xff1f; K_KKB_KKBCS_O…...

git 多个提交记录合并为一个

1.场景 有时候用devops等平台测试问题&#xff0c;需要多次修改小的记录提交&#xff0c;但是最终我们在合并主干的时候不想留那么多乱七八糟的记录&#xff0c;就需要在此分支合并这些提交记录&#xff0c;再合并到主干。 2.交互式变基 2.1 确定要合并的提交范围 # 查看最近…...

深入理解栈数据结构(Java实现):从原理到实战应用

在计算机科学的世界里&#xff0c;数据结构是构建高效程序的基石&#xff0c;而栈作为其中最基础且应用广泛的一种数据结构&#xff0c;其独特的 “后进先出&#xff08;LIFO&#xff09;” 特性&#xff0c;使其在众多领域发挥着关键作用。从算法设计到编译器实现&#xff0c;…...

支付宝 SEO 优化:提升小程序曝光与流量的完整指南

在拥有庞大用户基数的支付宝平台上&#xff0c;小程序已成为商家触达用户、提供服务的重要渠道。然而&#xff0c;随着平台上小程序数量的快速增长&#xff0c;如何在激烈的竞争中脱颖而出&#xff0c;获得更多的曝光和流量&#xff0c;成为每个开发者和运营者必须面对的关键挑…...

【leetcode100】最长重复子数组

1、题目描述 给两个整数数组 nums1 和 nums2 &#xff0c;返回 两个数组中 公共的 、长度最长的子数组的长度 。 示例 1&#xff1a; 输入&#xff1a;nums1 [1,2,3,2,1], nums2 [3,2,1,4,7] 输出&#xff1a;3 解释&#xff1a;长度最长的公共子数组是 [3,2,1] 。示例 2&…...

代码随想录算法训练营第五十六天| 图论2—卡码网99. 岛屿数量(dfs bfs)

假期归来继续刷题&#xff0c;图论第二天&#xff0c;主要是进一步熟悉dfs 和 bfs 的运用。 99. 岛屿数量&#xff08;dfs&#xff09; 99. 岛屿数量 ACM模式还是需要练&#xff0c;不过现在输入输出的感觉已经比较熟悉了。首先是要按照输入搭建一个grid&#xff0c;然后有一…...

源码示例:使用SpringBoot+Vue+ElementUI+UniAPP技术组合开发一套小微企业ERP系统

目录 一、系统架构设计 1、技术分层 2、开发环境 二、快速开发实践 1、后端搭建&#xff08;Spring Boot&#xff09; 2、前端管理端&#xff08;VueElementUI&#xff09; 3、移动端开发&#xff08;UniAPP&#xff09; 三、关键集成方案 1、统一接口处理 2、跨平台…...

基于Django框架的股票分红数据爬虫和展示系统

项目截图 一、项目简介 本项目是一个基于 Django 框架的股票分红数据爬虫和展示系统。它可以从东方财富网站爬取股票分红数据&#xff0c;并将数据存储到 Django 数据库中&#xff0c;同时提供数据查询、导出和图表展示功能。该系统为用户提供了一个方便的平台&#xff0c;用于…...

QT高级(1)QTableView自定义委托集合,一个类实现若干委托

自定义委托集合 1同系列文章2 功能3 源码 1同系列文章 QT中级&#xff08;1&#xff09;QTableView自定义委托&#xff08;一&#xff09;实现QSpinBox、QDoubleSpinBox委托 QT中级&#xff08;2&#xff09;QTableView自定义委托&#xff08;二&#xff09;实现QProgressBar委…...

kubectl系列(十一):top 查询pod连接数

在 Kubernetes 中&#xff0c;kubectl top 命令默认仅支持查看 Pod 或节点的 CPU/内存资源使用情况&#xff0c;并不直接提供 TCP 连接数的统计功能。若要获取 Pod 的 TCP 连接数&#xff0c;需结合其他工具和方法。以下是具体实现方案&#xff1a; 1. 直接进入容器查看 TCP 连…...

关于Spring

目录 事务篇 事务篇 先说结论 Spring事务实际上依赖的是Transactional接口和数据库的事务实现。 举个例子说&#xff0c;比如我们现在有一个**Service1类&#xff0c;这个类的方法MethodA执行一个向表A中插入数据&#xff1b;还有一个**Service2类&#xff0c;这个类的方法M…...

小家电专用WD5201 非隔离AC-DC稳压器|宽压80-305V|三档输出2.7/3.3/5V|多重安全保护

小家电专用WD5201 AC-DC稳压器&#xff5c;宽压80-305V&#xff5c;三档输出2.7/3.3/5V&#xff5c;多重安全保护 &#x1f4a5; WD5201&#xff0c;小家电电源的智能“稳压卫士”&#xff01; ✨ 核心卖点&#xff1a; ✅ 宽压兼容&#xff1a;输入 80-305V AC&#xff0c;电网…...

Docker 核心目录结构

1. Docker 核心目录结构 数据存储目录 默认根目录&#xff1a;/var/lib/docker Docker 所有运行时数据&#xff08;镜像、容器、卷、网络配置等&#xff09;的默认存储位置。 bash 复制 下载 # 查看 Docker 数据根目录 docker info | grep "Docker Root Dir" # 输出…...

源码分析之Leaflet中的LayerGroup

概述 LayerGroup是一个图层组&#xff0c;通过继承Layer基类&#xff0c;提供了一种管理多个图层&#xff08;如标记、多边形等&#xff09;的容器机制&#xff0c;比如地图的添加/移除操作等。 源码分析 源码实现 LayerGroup的源码实现如下&#xff1a; export var Layer…...

小芯片大战略:Chiplet技术如何重构全球半导体竞争格局?

在科技飞速发展的今天&#xff0c;半导体行业作为信息技术的核心领域之一&#xff0c;其发展速度和创新水平对全球经济的发展具有举足轻重的影响。然而&#xff0c;随着芯片制造工艺的不断进步&#xff0c;传统的单片集成方式逐渐遇到了技术瓶颈&#xff0c;如摩尔定律逐渐逼近…...

普通IT的股票交易成长史--股价起伏的真相-缺口(2)

声明&#xff1a;本文章的内容只是自己学习的总结&#xff0c;不构成投资建议。价格行为理论学习可参考简介中的几位&#xff0c;感谢他们的无私奉献。 送给自己的话&#xff1a; 仓位就是生命&#xff0c;绝对不能满仓&#xff01;&#xff01;&#xff01;&#xff01;&…...

MindSpore框架学习项目-ResNet药物分类-模型优化

目录 5.模型优化 5.1模型优化 6.结语 参考内容&#xff1a; 昇思MindSpore | 全场景AI框架 | 昇思MindSpore社区官网 华为自研的国产AI框架&#xff0c;训推一体&#xff0c;支持动态图、静态图&#xff0c;全场景适用&#xff0c;有着不错的生态 本项目可以在华为云modelar…...

基于阿里云DataWorks的物流履约时效离线分析

基于阿里云DataWorks的物流履约时效离线分析2. 数仓模型构建 ORC和Parquet区别&#xff1a; 压缩率与查询性能 压缩率 ORC通常压缩率更高&#xff0c;文件体积更小&#xff0c;适合存储成本敏感的场景。 Parquet因支持更灵活的嵌套结构&#xff0c;压缩率略…...

Kubernetes(k8s)学习笔记(八)--KubeSphere定制化安装

1执行下面的命令修改上一篇中yaml文件来实现定制化安装devops kubectl edit cm -n kubesphere-system ks-installer 主要是将devops几个配置由False改为True 然后使用下面的命令查看安装日志 kubectl logs -n kubesphere-system $(kubectl get pod -n kubesphere-system -l …...

养生:为健康生活筑牢根基

养生并非遥不可及的目标&#xff0c;而是贯穿于日常生活的点滴之中。从饮食、运动到心态调节&#xff0c;每一个环节都对我们的健康有着重要意义。以下为你详细介绍养生的实用策略&#xff0c;助力你开启健康生活模式。 饮食养生&#xff1a;科学搭配&#xff0c;滋养生命 合…...

Linux510 ssh服务 ssh连接

arning: Permanently added ‘11.1.1.100’ (ECDSA) to the list of known hosts. rooot11.1.1.100’s password: Permission denied, please try again. rooot11.1.1.100’s password: Permission denied, please try again 还没生效 登不上了 失效了 sshcaozx26成功登录 …...

关键点检测--使用YOLOv8对Leeds Sports Pose(LSP)关键点检测

目录 1. Leeds Sports Pose数据集下载2. 数据集处理2.1 获取标签2.2 将图像文件和标签文件处理成YOLO能使用的格式 3. 用YOLOv8进行训练3.1 训练3.2 预测 1. Leeds Sports Pose数据集下载 从kaggle官网下载这个数据集&#xff0c;地址为link&#xff0c;下载好的数据集文件如下…...

Elasticsearch内存管理与JVM优化:原理剖析与最佳实践

#作者&#xff1a;孙德新 文章目录 一、Elasticsearch缓存分类1、Node Query Cache&#xff1a;2、Shard Request Cache&#xff1a;3、Fielddata Cache&#xff1a; 三、内存常见的问题案例一案例二案例三案例四 四、内参分配最佳实践1、jvm heap分配2、将机器上少于一半的内…...