- 11.2 组件使用v-model
11.2 组件使用v-model
最后我们简单说说在父组件中使用v-model,可以先看结论,组件上使用v-model本质上是子父组件通信的语法糖。先看一个简单的使用例子。
var child = {template: '<div><input type="text" :value="value" @input="emitEvent">{{value}}</div>',methods: {emitEvent(e) {this.$emit('input', e.target.value)}},props: ['value']}new Vue({data() {return {message: 'test'}},components: {child},template: '<div id="app"><child v-model="message"></child></div>',el: '#app'})
父组件上使用v-model, 子组件默认会利用名为 value 的 prop 和名为 input 的事件,当然像select表单会以其他默认事件的形式存在。分析源码的过程也大致类似,这里只列举几个特别的地方。
AST生成阶段和普通表单控件的区别在于,当遇到child时,由于不是普通的html标签,会执行getComponentModel的过程,而getComponentModel的结果是在AST树上添加model的属性。
function model() {if (!config.isReservedTag(tag)) {genComponentModel(el, value, modifiers);}}function genComponentModel (el,value,modifiers) {var ref = modifiers || {};var number = ref.number;var trim = ref.trim;var baseValueExpression = '$$v';var valueExpression = baseValueExpression;if (trim) {valueExpression ="(typeof " + baseValueExpression + " === 'string'" +"? " + baseValueExpression + ".trim()" +": " + baseValueExpression + ")";}if (number) {valueExpression = "_n(" + valueExpression + ")";}var assignment = genAssignmentCode(value, valueExpression);// 在ast树上添加model属性,其中有value,expression,callback属性el.model = {value: ("(" + value + ")"),expression: JSON.stringify(value),callback: ("function (" + baseValueExpression + ") {" + assignment + "}")};}
最终AST树的结果:
{model: {callback: "function ($$v) {message=$$v}"expression: ""message""value: "(message)"}}
经过对AST树的处理后,回到genData$2的流程,由于有了model属性,父组件拼接的字符串会做进一步处理。
function genData$2 (el, state) {var data = '{';var dirs = genDirectives(el, state);···// v-model组件的render函数处理if (el.model) {data += "model:{value:" + (el.model.value) + ",callback:" + (el.model.callback) + ",expression:" + (el.model.expression) + "},";}···return data}
因此,父组件最终的render函数表现为:
"_c('child',{model:{value:(message),callback:function ($$v) {message=$$v},expression:"message"}})"
子组件的创建阶段照例会执行createComponent,其中针对model的逻辑需要特别说明。
function createComponent() {// transform component v-model data into props & eventsif (isDef(data.model)) {// 处理父组件的v-model指令对象transformModel(Ctor.options, data);}}
function transformModel (options, data) {// prop默认取的是value,除非配置上有model的选项var prop = (options.model && options.model.prop) || 'value';// event默认取的是input,除非配置上有model的选项var event = (options.model && options.model.event) || 'input'// vnode上新增props的属性,值为value;(data.attrs || (data.attrs = {}))[prop] = data.model.value;// vnode上新增on属性,标记事件var on = data.on || (data.on = {});var existing = on[event];var callback = data.model.callback;if (isDef(existing)) {if (Array.isArray(existing)? existing.indexOf(callback) === -1: existing !== callback) {on[event] = [callback].concat(existing);}} else {on[event] = callback;}}
从transformModel的逻辑可以看出,子组件vnode会为data.props 添加 data.model.value,并且给data.on 添加data.model.callback。因此父组件v-model语法糖本质上可以修改为'<child :value="message" @input="function(e){message = e}"></child>'
显然,这种写法就是事件通信的写法,这个过程又回到对事件指令的分析过程了。因此我们可以很明显的意识到,组件使用v-model本质上还是一个子父组件通信的语法糖。
