element中form组件prop嵌套属性的问题解决
Introduction
分享今天同事问的一个问题, 下面这段代码会报错,先看代码:重点是el-form-item组件的prop属性
<template> <div id="app"> <el-form label-width="100px" :model="ruleForm" :rules="rules"> <el-form-item v-for="(item, index) in tableData" :key="item.id" :prop="'tableData.' + index + '.name'" :rules="rules.name" > <el-input v-model="item.name"></el-input> </el-form-item> </el-form> </div> </template> <script> export default { name: "App", data() { return { ruleForm: { name: '' }, tableData: [ { id: 1, name: "" }, { id: 2, name: "" }, ], rules: { name: [ { required: true, message: "请输入活动名称", trigger: "blur", validator(rule, value, callback) { console.log("rule: ", rule); console.log("value: ", value); }, }, ], }, }; }, }; </script>
我第一眼看上去的时候并没有发现什么问题,但这段代码实实在在的报错了,我们来看一下错误
首先需要明确的是 这是一个警告, 并非一个error, 但他直接导致了我们的代码执行结果非预期,我们来分析一下这个错误
1.首先这个错误的第一句**Error in mounted hook**, 错误发生在mounted钩子中
2.请安排一个有效的path给prop
首先第一个问题,我的代码中并没有mounted函数,他怎么会报错呢?
第二个问题,让我们提供一个有效的prop, 但这里我们明明给的是有效的撒。
最后查了官网并查了百度 都没有找到很好的解决方式,最后没有办法,只能去看一下element-ui的源码, 下面是源码环节:
1.找到packages/form/src/form-item.vue这个文件
2. 我们根据他的报错来分析, 首先他说`mounted hook`中报错, 那我们就直接来看这个hook做了什么事情:
mounted() { if (this.prop) { this.dispatch('ElForm', 'el.form.addField', [this]); // 这一步不用管 let initialValue = this.fieldValue; // 取得fieldvalue // 判断fieldvalue是不是数组, 如果是数组则合并 if (Array.isArray(initialValue)) { initialValue = [].concat(initialValue); } // 给this定义一个initialValue属性 Object.defineProperty(this, 'initialValue', { value: initialValue }); this.addValidateEvents(); } }
我看这段代码的第一反应是, 这也没干什么事儿啊, 就取了个值 赋了个值, 看了一会儿我发现, 有一个盲点就是this.fieldValue
这里, 这是一个什么东西呢?不知道 去看一下。
computed: { fieldValue() { // 1.拿到当前"form"的model属性(这里很重要, 要记住这一步) const model = this.form.model; if (!model || !this.prop) { return; } // 2.拿到当前"form-item"的prop属性, // 也就是我们传的那个:prop="'tableData.' + index + '.name'" let path = this.prop; if (path.indexOf(':') !== -1) { path = path.replace(/:/, '.'); } // 3.将model和path传给了getPropByPath方法 return getPropByPath(model, path, true).v; } }
代码翻到fieldValue
这里, 发现这是一个computed
属性(步骤见注释), 发现最终返回getPropByPath
方法的返回结果, 我们接着去看一下这个方法.
我们发现这个方法是在utils/util
下的一个方法
第一眼看到这个方法, 是不是有一种眼熟的感觉?越看越像js的一个面试题
function getValue(obj, path) { ... } const obj = { a: { b: { c: '1' } } } getValue(obj, 'a.b.c'); // 1
有木有啊! 有木有!不能说一模一样,只能说分毫不差,既然知道它是面试题就简单了,我们首先需要明确 这个方法的作用就是 通过嵌套字符串key 拿到key对应的value, 那我们来看一下element是怎么做的。
首先先看第一句代码let tempObj = obj
, 这里第一次的obj是谁呢?是不是上面传过来的this.form.model
啊? 我们来看一下 我们代码中传输的model是什么
我们这里只需要记住, 我们传的是一个对象{ name: '' }
好的 我们再来看下一步, path = 正则匹配
, 最后的结果是keyArr = ['tableData', 0, 'name']
下面的代码就是走keyArr的循环了, 这里我们是3次循环, 因为keyArr只有三个元素
我们还是来捋一下:
1. 第一次循环, 此时的tempObj是 { name: '' }, key是tableData, key in tempObj?, 很显然是false, 所以直接走了else, 触发了throw new Error
其实看到这里我们就明白了, element在做prop
判断的时候, 是通过判断key
在不在model
中的方式 来判断path
是否合法的, 那我们知道这个原理之后, 只需要将我们的代码稍稍改动一下即可。
我们只需要将tableData
移到ruleForm
中即可, 然后我们再来看控制台已经不报错了。
总结
我考虑了一下element为什么要这样做,因为在这样的前提下,只看文档 应该不会得到有用的信息, 后来想了一会儿想通了, 因为element要判断prop
传递的值是否合法的话, 就只能用 一个obj
一个key
通过key in obj
这样的方式来判断, 而如果我们不把tableData
放到ruleForm
中, form-item
在mounted
的时候 是拿不到外面this
的data
的, 所以他无法判断 当前传进来的tableData
到底是谁, 也就没有办法使用key in obj
.