在前文vue中的时间提到了v-on指令。juejin.cn/post/686120…
vue中拥有大量指令方便开发者编写模板,绑定数据。
今天就来分析一下v-model。
根据文档cn.vuejs.org/v2/api/#v-m…。v-model是提供表单控件/组件上创建双向绑定用。
双向绑定
简单地理解就是数据变更触发视图更新,视图更新也会触发数据更新。
很多人都知道v-model就是一个@input与v-bind的语法糖,这篇文章也是从模板解析开始,一步步分析具体如何解析v-model的。
模板编译
遍历attributes,识别出vue指令后先读取修饰符,像v-model有lazy,number,trim。
将指令添加到AST上(再回顾一下)。
v-mode的写法为v-model="example",那么addDirective的传入参数如下:
el是当前节点,name是model, value为绑定的值,isDynamic为false,arg匹配不到值,modifiers为修饰符。
addDirective
AST节点上多一个directives属性,属性内包括节点的model指令。
genDirectives
AST生成之后要生成render函数,最主要的就是使用generate函数,在生成元素/组件的过程中会调用genDirectives函数。
参考:zhuanlan.zhihu.com/p/77901004
gen
cosnt gen = state.directives[dir.name]
这里的state是new CodegenState来的,接着进入src/platforms/web/compiler/directives/model.js文件,找到model也就是gen函数
真实节点的v-model
这里可以看出在模板编译的时候会根据是否为组件AST,普通节点AST做判断。这里先分析元素v-model。
Vue对select,checkbox,radio,textarea,input集中表单元素都做了处理,返回字符串函数。
这里的model函数是在gencode阶段的函数是对字ast做处理。不是后续的patch阶段涉及到的。
普通的输入框为例
以默认的input为例分析一下如何绑定数据。其实本质上就是添加属性与监听函数。
到此为止render函数已经生成完毕,接下来就是patch的过程。
Patch
同样地,platforms/web/runtime/patch.js中有
const patch = createPatchFunction({nodeOps, modules})
modules中关于指令的部分源自:vdom/modules/directives,导出含生命周期的对象**(非created周期)**
这里是对所有的指令进行一个更新,v-model只是指令之一
指令更新的时机与updateDOMListeners几乎是差不多,都是在节点创建/更新/销毁的时候调用。
_update
一般来说并不是在节点创建/更新/销毁的时候立即调用,而是会push到一个数组内,然后一次性遍历数组并调用。
这里涉及到多个hook,而真正的v-model指令只与insertend与componentUpdated有关。
v-model:insterted与composition
在组件创建/更新/销毁时候,都会把指令更新的动作push到一个数组中,然后再在指定的hook中一次性调用。
这里就看一下创建时候调用的inserted hook。
代码很简单不具体分析了,关键是看lazy修饰符处关于composition的处理。
这导致了v-model与:value + @input是不同的
这里也是第一次知道了composition事件:
developer.mozilla.org/zh-CN/docs/…
developer.mozilla.org/zh-CN/docs/…
第一个问题:默认指令是何时添加到vue原型上的?
normalizeDirectives与初始化指令
在_update函数中会先调用normalizeDirectives函数来获取指令对象。
VNode上的directive对象与vm实例上的directives对象组合返回。
那么vm.$options.directives是何时绑定的?
extend(Vue.options.directives, platformDirectives)
在src/platforms/web/runtime/index.js中往Vue.options.directives原型上扩展了平台对应的默认指令对象。
在web平台默认有show和model指令。src/platforms/web/directives/index
其中model对象就为
破案了
稍微梳理一下,在初始化的时候运行了runtime/index内的函数,扩展了options的directives对象,往对象上添加了各种默认指令,如model指令。其值为一个含有inserted属性与componentUpdated属性的对象。
因此在patch的过程中,每当create/update/destory的钩子函数触发的时候都能调用_update函数,而_update函数会通过normalizeDirectives函数找到了directives内部v-model指令对象
v-model指令对象上的方法会在组件patch周期内被调用。
其inserted方法才是真正执行addEventListener的方法。
组件的v-model
上文说了这么多都是关于真实节点的v-model。其实不论是组件还是真实节点,双向绑定的概念没有发生变化,那么是不是意味着实现方法其实也是差不多?
无非就是把原生事件换成了自定义事件
在编译阶段genCode的时候会调用genComponentModel方法,而非真实元素那样addHnadler,addProp
模板编译
genCode阶段:genComponentModel
并且往AST 节点上添加了model属性
在genCode最后阶段生成对应字符串
Patch:createComponent
在createComponent的时候会对此做处理
transformModel
在这里,vue默认组件是使用:value + @input的,
我们也可以自定义model对象来绑定不同的data与事件。不过需要注意的是,props的值与model.prop的值一定要一直,详情在下图中。
最后一问:自定义指令方法
上文一直反复提到过,在vue中用户可以自定义指令,那么什么时候扩展了该方法呢?
InitGlobalAPI
src/core/index文件中调用了inITGlobalAPI方法
initAssetRegisters
在自定义指令的时候,如果definition是 一个函数,那么指令只会在bind与update hook被调用的时候触发。
具体用法:cn.vuejs.org/v2/guide/cu…
小结
分析了vue从模板编译到最后patch过程是如何处理v-model指令的,其实根本上就是v-bind+v-on的语法糖,不过在输入事件时候与v-bind+v-on还是有细微差别的。
总体难度不大,只不过与事件一样,vue对DOM元素与组件进行v-model处理也太一样。