【vue源码04】深入学习vue源码实现——v-model指令的底层实现

在 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否则视为组件,添加 modelValueupdate:modelValue
32最后移除原始的 v-model 指令,避免运行时再次解析

三、总结对比

特性Vue2Vue3
实现方式运行时指令解析编译时转换为 :value + @input
自定义组件使用 value + input 事件使用 modelValue + update:modelValue
支持修饰符支持 .lazy.number.trim同样支持修饰符
源码文件src/platforms/web/compiler/directives/model.jspackages/compiler-dom/src/transforms/vModel.ts

四、附加知识:v-model 的演变(Vue2 → Vue3)

  • Vue2v-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 实际上是将 valueinput 事件进行了一层封装,对外表现为 v-model,内部则是属性和事件的代理。

3. 策略模式(Strategy Pattern)

  • v-model 支持多种修饰符(如 .lazy.number.trim),不同修饰符对应不同的处理策略。
    • .lazy:从 input 变为 change 事件。
    • .number:自动转换输入为数字。
    • .trim:自动去除首尾空格。

4. 组合模式(Composite Pattern)

  • 在 Vue3 中支持多个 v-model,例如 v-model:titlev-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 高频面试题

编号面试题答案简述
1v-model 是什么?它是如何工作的?是 Vue 的双向绑定语法糖,底层是 :value + @input 的组合。
2v-model 在原生 HTML 元素中是如何实现的?绑定 value 属性并监听 input 事件,更新数据。
3v-model 在自定义组件中是如何工作的?使用 modelValue 属性和 update:modelValue 事件进行通信。
4如何在组件中使用多个 v-model?(Vue3)使用 v-model:title 形式,每个 v-model 对应一个属性和事件。
5v-model 支持哪些修饰符?作用分别是什么?.lazy(change 触发)、.number(转为数字)、.trim(去空格)。
6v-model.sync 修饰符有什么区别?v-model 默认绑定 valueinput.sync 可以绑定任意属性并触发 update:prop
7v-model 是否可以在非 input 元素上使用?可以,但不会自动更新值,需手动绑定和触发事件。
8v-model 是否能绑定对象或数组?可以,但要注意引用类型的问题,推荐使用 refreactive 包装。
9如何在 Composition API 中使用 v-model使用 defineModel()(Vue3.4+)或手动绑定 modelValueupdate:modelValue
10v-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 选项,直接通过 propsemits 实现。

💡 使用方式(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>

这会自动声明 modelValueupdate:modelValue,并返回一个可响应的变量。


🧠 总结对比

特性Vue2Vue3
默认属性valuemodelValue
默认事件inputupdate:modelValue
多个 v-model不支持支持(如 v-model:title
是否需要 model 选项需要不需要
Composition API 支持不支持支持
defineModel()✅(Vue3.4+)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值