在 Vue 中,v-model
是一个语法糖,用于实现表单元素(如 <input>
、<textarea>
、<select>
)与组件数据之间的双向绑定。其底层原理是通过 :value
和 @input
的组合来实现的。
一、Vue2 中 v-model
底层源码实现
✅ 源码位置:
- 文件路径:
src/platforms/web/compiler/directives/model.js
- 函数名:
model()
指令处理函数
✅ 核心代码片段(简化版):
function model (el, binding, vnode) {
const value = binding.value;
const modifiers = binding.modifiers || {};
// 获取当前元素标签名
const tag = el.tag;
const type = el.attrsMap && el.attrsMap.type;
if (process.env.NODE_ENV !== 'production') {
// 在开发环境进行一些警告检查,比如 v-model 用在非 input 元素上
if (
el.attrsMap.hasOwnProperty('v-model') &&
el.tag === 'input' &&
type !== 'radio' &&
type !== 'checkbox'
) {
warn(`You should probably use "type='text'" on your input.`);
}
}
// 判断是否为原生 input/textarea 元素
if (tag === 'input' || tag === 'textarea') {
// 设置 :value="value"
addProp(el, 'value', `_s(${value})`);
// 设置 @input="value = $event.target.value"
addHandler(el, 'input', `var $$temp = $event.target.value;$emit('input', $$temp)`, null, true);
// 如果有 .lazy 修饰符,则监听 change 而不是 input
if (modifiers.lazy) {
addHandler(el, 'change', `$emit('input', $event.target.value)`);
} else if (modifiers.number) {
// 如果有 .number 修饰符,则尝试将输入转为数字
addHandler(el, 'input', `$emit('input', _n($event.target.value))`);
} else if (modifiers.trim) {
// 如果有 .trim 修饰符,则去除首尾空格
addHandler(el, 'input', `$emit('input', $event.target.value.trim())`);
}
} else {
// 对于自定义组件,相当于 :value + @input
addProp(el, 'value', `_s(${value})`);
addHandler(el, 'input', `$emit('input', $event)`);
}
}
✅ 注释说明:
行号 | 注释 |
---|---|
1 | 定义 model 函数,处理 v-model 指令 |
2 | 获取绑定的值和修饰符(如 lazy、number、trim) |
5 | 获取当前元素标签名,判断是 input 还是 textarea |
9-17 | 开发环境做一些类型校验和警告 |
20 | 判断是否为 input 或 textarea 原生元素 |
23 | 添加 value 属性绑定,即 :value="value" |
26 | 添加 input 事件处理,触发 input 事件并更新值 |
29 | 如果使用了 .lazy 修饰符,改为监听 change 事件 |
31 | 如果使用了 .number 修饰符,自动将输入转为数字 |
33 | 如果使用了 .trim 修饰符,自动去除首尾空格 |
36 | 否则认为是组件,直接绑定 value 并监听 input 事件 |
二、Vue3 中 v-model
底层源码实现
✅ 源码位置:
- 文件路径:
packages/compiler-dom/src/transforms/vModel.ts
- 函数名:
transformModel()
✅ 核心代码片段(简化版):
export function transformModel(node: ElementNode, context: TransformContext) {
const { tag, props } = node;
// 遍历所有属性,查找 v-model
for (let i = 0; i < props.length; i++) {
const prop = props[i];
if (prop.type === NodeTypes.DIRECTIVE && prop.name === 'model') {
// 获取绑定的值
const value = prop.exp;
// 如果是原生 input/textarea 元素
if (tag === 'input' || tag === 'textarea') {
const type = getAttribute(node, 'type');
const isCheckbox = type === 'checkbox';
const isRadio = type === 'radio';
// 添加 :value 绑定
node.props.push({
type: NodeTypes.ATTRIBUTE,
name: 'value',
value: createSimpleExpression(value.content, true)
});
// 添加 @input 事件
node.props.push({
type: NodeTypes.DIRECTIVE,
name: 'on',
arg: createSimpleExpression('input', true),
exp: createSimpleExpression(
`${value.content} = $event.target.value`,
false
)
});
} else {
// 对于组件,添加 :modelValue 和 @update:modelValue
node.props.push({
type: NodeTypes.ATTRIBUTE,
name: 'modelValue',
value: createSimpleExpression(value.content, true)
});
node.props.push({
type: NodeTypes.DIRECTIVE,
name: 'on',
arg: createSimpleExpression('update:modelValue', true),
exp: createSimpleExpression(
`${value.content} = $event`,
false
)
});
}
// 移除原始的 v-model 指令
props.splice(i, 1);
i--;
}
}
}
✅ 注释说明:
行号 | 注释 |
---|---|
1 | 定义 transformModel() 函数,编译时处理 v-model |
4 | 遍历节点属性,查找 v-model 指令 |
7 | 获取绑定的表达式(如 message ) |
10 | 判断是否为 input 或 textarea 元素 |
12 | 获取元素类型(如 checkbox、radio) |
15 | 添加 value 属性绑定 |
20 | 添加 input 事件处理,更新绑定值 |
24 | 否则视为组件,添加 modelValue 和 update:modelValue |
32 | 最后移除原始的 v-model 指令,避免运行时再次解析 |
三、总结对比
特性 | Vue2 | Vue3 |
---|---|---|
实现方式 | 运行时指令解析 | 编译时转换为 :value + @input |
自定义组件 | 使用 value + input 事件 | 使用 modelValue + update:modelValue |
支持修饰符 | 支持 .lazy 、.number 、.trim | 同样支持修饰符 |
源码文件 | src/platforms/web/compiler/directives/model.js | packages/compiler-dom/src/transforms/vModel.ts |
四、附加知识:v-model 的演变(Vue2 → Vue3)
- Vue2 中
v-model
默认绑定的是value
属性,并通过input
事件更新。 - Vue3 中为了支持多个
v-model
,引入了v-model:title
形式,默认仍然是v-model
等价于v-model:modelValue
。 - 这意味着你可以在一个组件中使用多个
v-model
,例如:
<template>
<MyComponent v-model:title="title" v-model:content="content" />
</template>
v-model
的设计模式、10 大应用案例与 10 大面试题
一、v-model
的设计模式解析
v-model
是 Vue 中一个非常实用的指令,它本质上是一种语法糖(Syntactic Sugar),用于简化双向数据绑定的写法。其底层实现融合了多种设计模式:
1. 观察者模式(Observer Pattern)
v-model
依赖于 Vue 的响应式系统,当数据变化时自动通知视图更新。- 表单元素通过监听
input
事件触发$emit('input')
,从而更新数据。
2. 代理模式(Proxy Pattern)
- 在组件中使用
v-model
时,Vue 实际上是将value
和input
事件进行了一层封装,对外表现为v-model
,内部则是属性和事件的代理。
3. 策略模式(Strategy Pattern)
v-model
支持多种修饰符(如.lazy
、.number
、.trim
),不同修饰符对应不同的处理策略。.lazy
:从input
变为change
事件。.number
:自动转换输入为数字。.trim
:自动去除首尾空格。
4. 组合模式(Composite Pattern)
- 在 Vue3 中支持多个
v-model
,例如v-model:title
、v-model:content
,这使得多个状态可以统一管理,形成复合型的数据绑定结构。
二、10 大 v-model
应用场景
编号 | 场景描述 | 示例 |
---|---|---|
1 | 表单输入框绑定 | <input v-model="username"> |
2 | 密码框绑定 | <input type="password" v-model="password"> |
3 | 多行文本绑定 | <textarea v-model="content"></textarea> |
4 | 单选框绑定 | <input type="radio" v-model="gender" value="male"> |
5 | 复选框绑定 | <input type="checkbox" v-model="agree"> |
6 | 下拉选择框绑定 | <select v-model="selectedOption"><option>...</option></select> |
7 | 自定义组件通信 | <MyInput v-model="searchText" /> |
8 | 多个 v-model 同时使用(Vue3) | <MyComponent v-model:title="title" v-model:content="content" /> |
9 | 表单校验联动 | <input v-model.trim="email"> + 正则验证 |
10 | 动态表单项绑定 | <div v-for="item in list"><input v-model="item.value"></div> |
三、10 大 v-model
高频面试题
编号 | 面试题 | 答案简述 |
---|---|---|
1 | v-model 是什么?它是如何工作的? | 是 Vue 的双向绑定语法糖,底层是 :value + @input 的组合。 |
2 | v-model 在原生 HTML 元素中是如何实现的? | 绑定 value 属性并监听 input 事件,更新数据。 |
3 | v-model 在自定义组件中是如何工作的? | 使用 modelValue 属性和 update:modelValue 事件进行通信。 |
4 | 如何在组件中使用多个 v-model ?(Vue3) | 使用 v-model:title 形式,每个 v-model 对应一个属性和事件。 |
5 | v-model 支持哪些修饰符?作用分别是什么? | .lazy (change 触发)、.number (转为数字)、.trim (去空格)。 |
6 | v-model 和 .sync 修饰符有什么区别? | v-model 默认绑定 value 和 input ,.sync 可以绑定任意属性并触发 update:prop 。 |
7 | v-model 是否可以在非 input 元素上使用? | 可以,但不会自动更新值,需手动绑定和触发事件。 |
8 | v-model 是否能绑定对象或数组? | 可以,但要注意引用类型的问题,推荐使用 ref 或 reactive 包装。 |
9 | 如何在 Composition API 中使用 v-model ? | 使用 defineModel() (Vue3.4+)或手动绑定 modelValue 和 update:modelValue 。 |
10 | v-model 是否是响应式的? | 是的,它基于 Vue 的响应式系统,数据变化会自动触发视图更新。 |
四、总结
模块 | 内容 |
---|---|
设计模式 | 观察者、代理、策略、组合 |
应用场景 | 表单绑定、组件通信、动态数据绑定等 10 种 |
高频面试题 | 原理、修饰符、多 model、自定义组件、Composition API 支持等 10 道 |
在 Vue 中,v-model
在自定义组件中并不是像原生 <input>
那样自动支持的。它是一种语法糖,底层是通过 :value
和 @input
的组合来实现双向绑定。
知道了v-model的具体实现后,老曹再给大家分别封装下vue2和vue3的自定义组件,分别实现v-model的具体功能以供参考。
✅ 一、Vue2 中 v-model
在自定义组件中的实现
🎯 实现目标:
让组件支持如下写法:
<MyInput v-model="message" />
等价于:
<MyInput :value="message" @input="message = $event" />
📁 示例代码:
1. 自定义组件 MyInput.vue
<template>
<input
type="text"
:value="value"
@input="$emit('input', $event.target.value)"
/>
</template>
<script>
export default {
name: 'MyInput',
props: ['value'],
model: {
prop: 'value',
event: 'input'
}
}
</script>
model
是 Vue2 提供的一个选项,用于指定v-model
对应的属性名和事件名。- 如果不写
model
,默认依然是value
+input
。
💡 使用方式:
<template>
<div>
<MyInput v-model="message" />
<p>输入的内容:{{ message }}</p>
</div>
</template>
<script>
import MyInput from './MyInput.vue'
export default {
components: { MyInput },
data() {
return {
message: ''
}
}
}
</script>
✅ 二、Vue3 中 v-model
在自定义组件中的实现
🎯 实现目标:
Vue3 支持多个 v-model
,例如:
<MyComponent v-model:title="title" v-model:content="content" />
但最基本的用法仍然兼容 Vue2:
<MyInput v-model="message" />
📁 示例代码:
1. 自定义组件 MyInput.vue
(Vue3)
<template>
<input
type="text"
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
</template>
<script>
export default {
name: 'MyInput',
props: ['modelValue'],
emits: ['update:modelValue']
}
</script>
- Vue3 默认使用
modelValue
属性 +update:modelValue
事件作为v-model
的映射。 - 不再需要
model
选项,直接通过props
和emits
实现。
💡 使用方式(Vue3):
<template>
<div>
<MyInput v-model="message" />
<p>输入的内容:{{ message }}</p>
</div>
</template>
<script>
import MyInput from './MyInput.vue'
export default {
components: { MyInput },
data() {
return {
message: ''
}
}
}
</script>
✅ 三、Vue3 Composition API 实现 v-model
如果你使用的是 <script setup>
,可以这样写:
🔧 MyInput.vue
(Composition API)
<template>
<input
type="text"
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
</template>
<script setup>
defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
</script>
🔧 使用组件:
<template>
<MyInput v-model="message" />
</template>
<script setup>
import { ref } from 'vue'
const message = ref('')
</script>
✅ 四、Vue3.4+ 新特性:defineModel()
(实验性)
Vue 3.4 引入了 defineModel()
,进一步简化 v-model
的处理:
<template>
<input v-model="text" />
</template>
<script setup>
const text = defineModel()
</script>
这会自动声明 modelValue
和 update:modelValue
,并返回一个可响应的变量。
🧠 总结对比
特性 | Vue2 | Vue3 |
---|---|---|
默认属性 | value | modelValue |
默认事件 | input | update:modelValue |
多个 v-model | 不支持 | 支持(如 v-model:title ) |
是否需要 model 选项 | 需要 | 不需要 |
Composition API 支持 | 不支持 | 支持 |
defineModel() | ❌ | ✅(Vue3.4+) |