Vue之v-model

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

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

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

为了更加直观,我们结合一个例子来分析:

v-model简单demo,在input标签上设置了v-model属性,绑定message

编译-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],state是codegenState类(compiler/codegen/index.js)的一个实例,和编译息息相关,其中有一个directives实例对象,是由baseDirectives和options.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取到修饰符,根据修饰符的不同,影响eventvalueExpression的值。对于我们的例子,event为input,valueExpression为$event.target.value。然后去执行 genAssignmentCode 去生成代码(src/compiler/directives/model.js)

genAssignmentCode

        genAssignmentCode,根据参数描述去生成代码。该方法首先对 v-model 对应的 value 做了解析,它处理了非常多的情况,对我们的例子,value 就是 messgae,所以返回的 res.key 为 null,然后我们就得到 ${value}=${assignment},也就是 message=$event.target.value。然后我们又命中了 needCompositionGuard 为 true 的逻辑,所以最终的code为if($event.target.composing)return;message=$event.target.value。

code 生成完后,又执行了 2 句非常关键的代码:

关键代码

        这实际上就是 input 实现 v-model 的精髓,通过修改 AST 元素,给 el 添加一个 prop,相当于我们在 input 上动态绑定了 value又给 el 添加了事件处理,相当于在 input 上绑定了 input 事件,其实转换成模板如下:

在input上动态绑定value,并给其添加了事件处理

        其实就是动态绑定了 input 的 value 指向了 messgae 变量,并且在触发 input 事件的时候去动态把 message 设置为目标值,这样实际上就完成了数据双向绑定了,所以说 v-model 实际上就是语法糖。

        再回到 genDirectives,它接下来的逻辑就是根据指令生成一些 data 的代码:

生成data代码

对我们的例子而言,最终生成的 render 代码如下:

最终生成的 render 代码

        关于事件的处理我们之前的章节已经分析过了,所以对于 input 的 v-model 而言,完全就是语法糖,并且对于其它表单元素套路都是一样,区别在于生成的事件代码会略有不同

        v-model 除了作用在表单元素上,新版的 Vue 还把这一语法糖用在了组件上,接下来我们来分析它的实现。

组件:

为了更加直观,我们也是通过一个例子分析:

父组件引用child子组件的地方使用了v-model关联了数据message。而子组件定义了一个value的prop,并且在input事件的回调函数中,通过this.$emit('input', e.target.value)派发了一个事件

        可以看到,父组件引用 child 子组件的地方使用了 v-model 关联了数据 message;而子组件定义了一个 value 的 prop,并且在 input 事件的回调函数中,通过 this.$emit('input', e.target.value) 派发了一个事件,为了让 v-model 生效,这两点是必须的。

        接着我们从源码角度分析实现原理,还是从编译阶段说起,对于父组件而言,在编译阶段会解析 v-modle 指令,依然会执行 genData 函数中的 genDirectives 函数,接着执行 src/platforms/web/compiler/directives/model.js 中定义的 model 函数,并命中如下逻辑:

model 函数中命中的逻辑

genComponentModel 函数定义在 src/compiler/directives/model.js :

genComponentModel函数

genComponentModel 的逻辑很简单,对我们的例子而言,生成的 el.model 的值为:

生成的 el.model 

那么在 genDirectives 之后,genData 函数中有一段逻辑如下:

genData

那么父组件最终生成的 render 代码如下:

 render 代码

        然后在创建子组件 vnode 阶段,会执行 createComponent 函数,它的定义在 src/core/vdom/create-component.js 中:

createComponent

其中会对 data.model 的情况做处理,执行 transformModel(Ctor.options, data) 方法:

transformModel

        transformModel 逻辑很简单,给 data.props 添加 data.model.value,并且给data.on 添加 data.model.callback,对我们的例子而言,扩展结果如下:

transformModel结果

其实就相当于我们在这样编写父组件:

相当于这样

        子组件传递的 value 绑定到当前父组件的 message,同时监听自定义 input 事件,当子组件派发 input 事件的时候,父组件会在事件回调函数中修改 message 的值,同时 value 也会发生变化,子组件的 input 值被更新。

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

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

子组件的 value prop 以及派发的 input 事件名是可配的

        也就是说可以在定义子组件的时候通过 model 选项配置子组件接收的 prop 名以及派发的事件名,举个例子:

子组件修改了接收的 prop 名以及派发的事件名,然而这一切父组件作为调用方是不用关心的,这样做的好处是我们可以把 value 这个 prop 作为其它的用途。

总结

        那么至此,v-model 的实现就分析完了,我们了解到它是 Vue 双向绑定的真正实现,但本质上就是一种语法糖,它即可以支持原生表单元素,也可以支持自定义组件。在组件的实现中,我们是可以配置子组件接收的 prop 名称,以及派发的事件名称。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,165评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,503评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,295评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,589评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,439评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,342评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,749评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,397评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,700评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,740评论 2 313
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,523评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,364评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,755评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,024评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,297评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,721评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,918评论 2 336

推荐阅读更多精彩内容

  • 这篇笔记主要包含 Vue 2 不同于 Vue 1 或者特有的内容,还有我对于 Vue 1.0 印象不深的内容。关于...
    云之外阅读 5,042评论 0 29
  • vue概述 在官方文档中,有一句话对Vue的定位说的很明确:Vue.js 的核心是一个允许采用简洁的模板语法来声明...
    li4065阅读 7,178评论 0 25
  • 深入响应式 追踪变化: 把普通js对象传给Vue实例的data选项,Vue将使用Object.defineProp...
    冥冥2017阅读 4,831评论 6 16
  • VUE介绍 Vue的特点构建用户界面,只关注View层简单易学,简洁、轻量、快速渐进式框架 框架VS库库,是一封装...
    多多酱_DuoDuo_阅读 2,680评论 1 17
  • 最近天寒地冻!不经让人不寒而栗,裹紧身上的大衣,北京的天气很怪异! 以前是“海市蜃楼,进入仙境般,更有人调侃到,美...
    曲沙南风阅读 220评论 1 2