Vue 2.0+之 v-model实现原理

本文深入讲解Vue中v-model的工作机制,包括其本质及在不同场景下的实现方式,帮助理解Vue的双向绑定。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目标:1、了解v-model的本质。2、了解v-model的实现原理。

我们知道Vue的核心特性之一是双向绑定,vue的响应式原理是实现了数据->视图,接下来我们要学习 视图->数据的原理。

·v-model是一个指令,限制在<input>、<select>、<textarea>、components中使用,修饰符.lazy(取代 input 监听change 事件)、.number(输入字符串转为有效的数字)、.trim(输入首尾空格过滤)。它其实是一个语法糖,接下来我们就来分析v-model的实现原理。

为了更加直观,我们结合一个例子来分析:
在这里插入图片描述

编译-parser

创建AST节点后会对节点做处理,其中对属性的处理会执行到processAttrs(el)。其中会遍历节点上的attrsList,拿到name属性,先判断name是否匹配模版指令的正则表达式(比如v-,:),如果匹配到,给节点的hasBindings属性设为true(标志是动态节点)。

然后通过parseModifiers(name)取到属性描述符 对象modifers。接下来会对指令进行判断命中'v-bind'?'v-on'?'v-model'。我们命中'v-model',把'v-model'去掉,接着执addDirective(el,name,rawName,value,arg,modifiers),把我们传的参数添加到el.directives数组中。我们把v-model相关参数传入到el.directives中,为后续codegen准备。

编译-codegen:

genData函数中会执行const dirs = genDirectives(el, state)(src/compiler/codegen/index.js)。

  1. 遍历 el.directives,获取每一个指令对应的方法。首先他会拿前面的directives属性,如果存在,会开始为之后拼接做准备,拼接res = ‘directives:[’。我们遍历directives,const gen = state.directives[dir.name],statecodegenState类(compiler/codegen/index.js)的一个实例,和编译息息相关,其中有一个directives实例对象,是由baseDirectivesoptions.directives做合并。options是和编译相关的配置(和编译平台有关)。state.directives最终拿到的是一个[model,text,html],一个由三个对象组成的数组。在我们这里model对应一个model函数(platform/web/compiler/directives/model.js)。如果拿到了model函数,我们会执行这个model函数,needRuntime = !!gen(el, dir, state.warn)

  2. 获取到指令方法就执行。model(ast节点,directives)函数,首先去取得value值,modifers修饰符,标签名等。接来下,根据tag(input)和type(textarea)做判断执行不同的逻辑,在我们的例子中会命中genDefaultModel(el, value, modifiers)

    genDefaultModel方法,通过modifiers取到修饰符,根据修饰符的不同,影响event和valueExpression的值。对于我们的例子,eventinputvalueExpression$event.target.value。然后去执行 genAssignmentCode 去生成代码(src/compiler/directives/model.js)
    在这里插入图片描述
    genAssignmentCode,根据参数描述去生成代码。该方法首先对 v-model 对应的 value 做了解析,它处理了非常多的情况,对我们的例子,value 就是 messgae,所以返回的 res.key 为 null,然后我们就得到 value={value}=value={assignment},也就是 message=event.target.value。然后我们又命中了needCompositionGuard为true的逻辑,所以最终的code为if(event.target.value。然后我们又命中了 needCompositionGuard 为 true 的逻辑,所以最终的code为if(event.target.valueneedCompositionGuardtruecodeif(event.target.composing)return;message=$event.target.value。

code 生成完后,又执行了 2 句非常关键的代码:
在这里插入图片描述
这实际上就是 input实现v-model的精髓,通过修改 AST 元素,给 el 添加一个 prop,相当于我们在 input 上动态绑定了 value,又给 el 添加了事件处理,相当于在 input 上绑定了 input 事件,其实转换成模板如下:
在这里插入图片描述
其实就是动态绑定了 inputvalue 指向了 messgae 变量,并且在触发input事件的时候去动态把message设置为目标值,这样实际上就完成了数据双向绑定了,所以说v-model实际上就是语法糖。

再回到 genDirectives,它接下来的逻辑就是根据指令生成一些 data 的代码:在这里插入图片描述
在这里插入图片描述
对我们的例子而言,最终生成的 render 代码如下:
在这里插入图片描述
v-model 除了作用在表单元素上,新版的 Vue 还把这一语法糖用在了组件上,接下来我们来分析它的实现。

组件:

为了更加直观,我们也是通过一个例子分析:
在这里插入图片描述
可以看到,父组件引用 child子组件的地方使用了 v-model关联了数据 message;而子组件定义了一个valueprop,并且在 input 事件的回调函数中,通过this.$emit('input', e.target.value)派发了一个事件,为了让 v-model 生效,这两点是必须的。

接着我们从源码角度分析实现原理,还是从编译阶段说起,对于父组件而言,在编译阶段会解析v-modle指令,依然会执行 genData 函数中的 genDirectives 函数,接着执行 src/platforms/web/compiler/directives/model.js 中定义的 model 函数,并命中如下逻辑:
在这里插入图片描述
genComponentModel函数定义在 src/compiler/directives/model.js :
在这里插入图片描述
genComponentModel的逻辑很简单,对我们的例子而言,生成的 el.model 的值为:
在这里插入图片描述
那么在 genDirectives 之后,genData 函数中有一段逻辑如下:
在这里插入图片描述
那么父组件最终生成的 render 代码如下:
在这里插入图片描述
然后在创建子组件 vnode阶段,会执行 createComponent函数,它的定义在 src/core/vdom/create-component.js 中:
在这里插入图片描述
其中会对 data.model 的情况做处理,执行 transformModel(Ctor.options, data) 方法:
在这里插入图片描述
transformModel逻辑很简单,给 data.props添加 data.model.value,并且给data.on 添加 data.model.callback,对我们的例子而言,扩展结果如下:
在这里插入图片描述
其实就相当于我们在这样编写父组件:
在这里插入图片描述
子组件传递的 value 绑定到当前父组件的 message,同时监听自定义 input事件,当子组件派发input 事件的时候,父组件会在事件回调函数中修改 message 的值,同时 value 也会发生变化,子组件的 input 值被更新。

这就是典型的 Vue 的父子组件通讯模式,父组件通过 prop 把数据传递到子组件,子组件修改了数据后把改变通过 $emit事件的方式通知父组件,所以说组件上的 v-model 也是一种语法糖。

另外我们注意到组件 v-model 的实现,子组件的 value prop以及派发的 input 事件名是可配的,可以看到 transformModel 中对这部分的处理:
在这里插入图片描述

也就是说可以在定义子组件的时候通过 model 选项配置子组件接收的 prop 名以及派发的事件名,举个例子:
在这里插入图片描述
总结
那么至此,v-model 的实现就分析完了,我们了解到它是 Vue 双向绑定的真正实现,但本质上就是一种语法糖,它即可以支持原生表单元素,也可以支持自定义组件。在组件的实现中,我们是可以配置子组件接收的 prop 名称,以及派发的事件名称。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值