您现在的位置是:网站首页> 编程资料编程资料
vue动态绑定v-model属性名方式_vue.js_
2023-05-24
406人已围观
简介 vue动态绑定v-model属性名方式_vue.js_
vue动态绑定v-model属性名
1.目标
首先配置列,根据配置渲染表单,每个表单项绑定配置中的属性
2.方案
- v-model绑定的必须是属性,可以使用方括号,取指定对象的属性
亲测有效
vue双向绑定原理(v-model)
之前有整理过Vue响应式原理,响应式主要的效果是数据改变了就会引起页面修改。关于v-model我们也不陌生,vue的双向绑定指令,页面修改会引起数据修改,数据修改页面也会跟着改变。我们直到数据->页面是由vue的响应式原理实现的,那么该怎么做到页面->数据的修改呢?
表单绑定
v-model一般我们是在表单元素上进行使用,因为视图能影响数据,本质上是这个视图需要可交互,因此表单是实现这一交互的前提。表单的使用是以、
这里我们从模板解析开始分析,vue对v-model做了什么操作。
这里我们来看一些绑定在input上的v-model都经历了什么。
// 普通输入框
AST树的解析
模版的编译阶段,会调用var ast = parse(template.trim(), options)生成AST树,parse函数的起他细节这里不展开分析,我们只说模板属性上的解析processAttrs函数。
vue模板属性有两部分组成,一部分是指令,另一部分是普通的html标签属性。对于指令,出去v-on和v-bind,其他普通指令会执行addDirective过程。
// 处理模板属性 function processAttrs(el) { var list = el.attrsList; var i, l, name, rawName, value, modifiers, syncGen, isDynamic; for (i = 0, l = list.length; i < l; i++) { name = rawName = list[i].name; // v-on:click value = list[i].value; // doThis if (dirRE.test(name)) { // 1.针对指令的属性处理 ··· if (bindRE.test(name)) { // v-bind分支 ··· } else if(onRE.test(name)) { // v-on分支 ··· } else { // 除了v-bind,v-on之外的普通指令 ··· // 普通指令会在AST树上添加directives属性 addDirective(el, name, rawName, value, arg, isDynamic, modifiers, list[i]); if (name === 'model') { checkForAliasModel(el, value); } } } else { // 2. 普通html标签属性 } } }在AST产生阶段对事件指令v-on的处理是为AST树添加events属性。类似的,普通指令会在AST树上添加directives属性,我们可以看一下addDirective函数
// 添加directives属性 function addDirective (el,name,rawName,value,arg,isDynamicArg,modifiers,range) { (el.directives || (el.directives = [])).push(rangeSetItem({ name: name, rawName: rawName, value: value, arg: arg, isDynamicArg: isDynamicArg, modifiers: modifiers // 模板中添加的修饰符,如:.lazy、.number、.trim }, range)); el.plain = false; }最终AST树上会多处一个属性对象
// AST { directives: { { rawName: 'v-model', value: 'value', name: 'v-model', modifiers: undefined } } }render函数生成
render函数生成阶段,generate逻辑,其中genData会对模版的各个属性进行处理,最终返回拼接好的字符串模板,而对指令的处理会进入genDirectives函数
在genDirectives函数中,会拿到之前AST树中的directives对象,并遍历解析指令对象,最终以'directives:['包裹的字符串返回。
// directives render字符串的生成 function genDirectives (el, state) { // 拿到指令对象 var dirs = el.directives; if (!dirs) { return } // 字符串拼接 var res = 'directives:['; var hasRuntime = false; var i, l, dir, needRuntime; for (i = 0, l = dirs.length; i < l; i++) { dir = dirs[i]; needRuntime = true; // 对指令ast树的重新处理 var gen = state.directives[dir.name]; if (gen) { // compile-time directive that manipulates AST. // returns true if it also needs a runtime counterpart. needRuntime = !!gen(el, dir, state.warn); } if (needRuntime) { hasRuntime = true; res += "{name:\"" + (dir.name) + "\",rawName:\"" + (dir.rawName) + "\"" + (dir.value ? (",value:(" + (dir.value) + "),expression:" + (JSON.stringify(dir.value))) : '') + (dir.arg ? (",arg:" + (dir.isDynamicArg ? dir.arg : ("\"" + (dir.arg) + "\""))) : '') + (dir.modifiers ? (",modifiers:" + (JSON.stringify(dir.modifiers))) : '') + "},"; } } if (hasRuntime) { return res.slice(0, -1) + ']' } }这里有一句关键代码var gen = state.directives[dir.name],这里的的dir.name为model,这个model回去执行对应的model函数。我们来看一下model函数的逻辑。
unction model (el,dir,_warn) { warn$1 = _warn; // 绑定的值 var value = dir.value; var modifiers = dir.modifiers; var tag = el.tag; var type = el.attrsMap.type; { // 这里遇到type是file的html,如果还使用双向绑定会报出警告。 // 因为File inputs是只读的 if (tag === 'input' && type === 'file') { warn$1( "<" + (el.tag) + " v-model=\"" + value + "\" type=\"file\">:\n" + "File inputs are read only. Use a v-on:change listener instead.", el.rawAttrsMap['v-model'] ); } } //组件上v-model的处理 if (el.component) { genComponentModel(el, value, modifiers); // component v-model doesn't need extra runtime return false } else if (tag === 'select') { // select表单 genSelect(el, value, modifiers); } else if (tag === 'input' && type === 'checkbox') { // checkbox表单 genCheckboxModel(el, value, modifiers); } else if (tag === 'input' && type === 'radio') { // radio表单 genRadioModel(el, value, modifiers); } else if (tag === 'input' || tag === 'textarea') { // 普通input,如 text, textarea genDefaultModel(el, value, modifiers); } else if (!config.isReservedTag(tag)) { genComponentModel(el, value, modifiers); // component v-model doesn't need extra runtime return false } else { // 如果不是表单使用v-model,同样会报出警告,双向绑定只针对表单控件。 warn$1( "<" + (el.tag) + " v-model=\"" + value + "\">: " + "v-model is not supported on this element type. " + 'If you are working with contenteditable, it\'s recommended to ' + 'wrap a library dedicated for that purpose inside a custom component.', el.rawAttrsMap['v-model'] ); } // ensure runtime directive metadata // return true }我们可以看到对于v-model的处理,在这一步上会根据使用场景处理调用不同的处理。单是对每种类型对应的事件处理响应机制也不同。因此我们需要针对不同的表单控件生成不同的render函数,所以需要产生不同的AST属性。model针对不同类型的表单控件有不同的处理分支。我们来看普通input标签的处理,genDefalutModel分支。
function genDefaultModel (el,value,modifiers) { var type = el.attrsMap.type; // v-model和v-bind值相同值,有冲突会报错 { var value$1 = el.attrsMap['v-bind:value'] || el.attrsMap[':value']; var typeBinding = el.attrsMap['v-bind:type'] || el.attrsMap[':type']; if (value$1 && !typeBinding) { var binding = el.attrsMap['v-bind:value'] ? 'v-bind:value' : ':value'; warn$1( binding + "=\"" + value$1 + "\" conflicts with v-model on the same element " + 'because the latter already expands to a value binding internally', el.rawAttrsMap[binding] ); } } // modifiers存贮的是v-model的修饰符。 var ref = modifiers || {}; // lazy,trim,number是可供v-model使用的修饰符 var lazy = ref.lazy; var number = ref.number; var trim = ref.trim; var needCompositionGuard = !lazy && type !== 'range'; // lazy修饰符将触发同步的事件从input改为change var event = lazy ? 'change' : type === 'range' ? RANGE_TOKEN : 'input'; var valueExpression = '$event.target.value'; // 过滤用户输入的首尾空白符 if (trim) { valueExpression = "$event.target.value.trim()"; } // 将用户输入转为数值类型 if (number) { valueExpression = "_n(" + valueExpression + ")"; } // genAssignmentCode函数是为了处理v-model的格式,允许使用以下的形式: v-model="a.b" v-model="a[b]" var code = genAssignmentCode(value, valueExpression); if (needCompositionGuard) { // 保证了不会在输入法组合文字过程中得到更新 code = "if($event.target.composing)return;" + code; } // 添加value属性 addProp(el, 'value', ("(" + value + ")")); // 绑定事件 addHandler(el, event, code, null, true); if (trim || number) { addHandler(el, 'blur', '$forceUpdate()'); } } function genAssignmentCode (value,assignment) { // 处理v-model的格式,v-model="a.b" v-model="a[b]" var res = parseModel(value); if (res.key === null) { // 普通情形 return (value + "=" + assignment) } else { // 对象形式 return ("$set(" + (res.exp) + ", " + (res.key) + ", " + assignment + ")") } }该函数主要逻辑是两个部分,一部分是针对修饰符产生不同的事件处理字符串,二是为v-model产生的AST树,添加属性和事件相关的属性。其中最核心的两行代码是:
// 添加value属性 addProp(el, 'value', ("(" + value + ")")); // 绑定事件属性 addHandler(el, event, code, null, true);addHandler函数会为AST树添加事件相关的属性,addProp会为AST树添加props属性。最终AST树新增了两个属性。
到这里我们会发现,通过genDirective处理后,原先的AST树新增了两个属性。所以在字符串生成阶段同样需要处理props和event的分支
function genData$2 (el, state) { var data = '{'; // 已经分析过的genDirectives var dirs = genDirectives(el, state); // 处理props if (el.props) { data += "domProps:" + (genProps(el.props)) + ","; } // 处理事件 if (el.events) { data += (genHandlers(el.events, false)) + ","; } }最终render函数的结果为:
"_c('input', { directives:[{ name:"model", rawName:"v-model", value:(message), expression:"message" }], attrs:{"type":"text"}, domProps:{"value":(message)}, on:{ "input":function($event){
相关内容
- vue中render函数和h函数以及jsx的使用方式_vue.js_
- element-ui下拉菜单组件Dropdown的示例代码_vue.js_
- vue3新增Teleport的问题_vue.js_
- vue中使用ts配置的具体步骤_vue.js_
- vue中环境变量的使用与配置讲解_vue.js_
- vue.js动态设置VueComponent高度遇到的问题及解决_vue.js_
- Vue3 elementUI如何修改el-date-picker默认时间_vue.js_
- 在vue中使用echarts实现飞机航线水滴图词云图效果_vue.js_
- Vue如何根据id在数组中取出数据_vue.js_
- node+express+axios实现单文件上传功能_node.js_
点击排行
本栏推荐
