vue指令解析:v-model

993 阅读5分钟

在前文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指令只与insertendcomponentUpdated有关。

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处理也太一样。