关于ElementUI 表单值类型检测失败的问题

刚刚开始接触Vue以及ElementUI,最近在开发过程中遇到了问题,经历了各种痛苦的debug....直接入正题把:

这里好像不可以传文件,我就直接贴代码了:

<template>
    <div>
        <el-button @click="onSelect">点我</el-button>

        <el-dialog
        title="弹窗"
        :visible.sync="dialogVisible"
        width="50%">
            <el-form 
            ref="testForm" 
            :model="formData" 
            :rules="formRules">
                <el-form-item label="类型">
                    <el-select v-model="formData.class">
                        <el-option label="类型1" value="class1"></el-option>
                        <el-option label="类型2" value="class2"></el-option>
                    </el-select>
                </el-form-item>
                <el-form-item label="class1" v-if="formData.class=='class1'" prop="text">
                    <el-input type="textarea" v-model="formData.text"></el-input>
                </el-form-item>
                <el-form-item label="类型2" v-else prop="value">
                    <el-select v-model="formData.value" multiple>
                        <el-option label="sdf" value="11"></el-option>
                        <el-option label="dsaf" value="22"></el-option>
                        <el-option label="dyrt" value="33"></el-option>
                    </el-select>
                </el-form-item>
                <el-form-item>
                    <el-button type="primary" @click="onSubmit">ok</el-button>
                </el-form-item>
            </el-form>
        </el-dialog>
    </div>
</template>

<script>
export default {
    data(){
        return {
            dialogVisible: false,
            formData: {
                class: '',
                text: '',
                value: []
            },
            formRules: {
                text: [{
                    required:true,
                    validator(rule, value, callback){
                        var Reg = /[\w+\/]*\w+/;
                        if(value==''){
                            callback(new Error('该字段不能为空'))
                        }else if(!value.match(Reg) || value.match(Reg)!=value){
                            callback(new Error('请输入正确的用户名,用户名之间用/隔开'));
                        }else{
                            callback();
                        }
                    }
                }],
                value: [{
                    required:true,
                    message:'不能为空'
                }]
            }
        }
    },
    methods: {
        onSubmit(){},
        onSelect(){
            this.dialogVisible = true;
            this.$nextTick(() => {
                this.$refs.testForm.resetFields();
            });
            console.log(this.formData)
        }
    }
}
</script>

这个组件就是一个按钮,点击按钮会出现一个弹窗,图如下:

clipboard.png

由于是提问题,尽量省去了无关代码,包括自定义组件以及css代码,所有的元素都由ElementUI提供。

问题重现步骤:
1.点开弹窗,选择类型为类型1,此时文本输入域会变为textarea,关闭弹窗。
2.再次点击按钮打开弹窗,控制台会报错,类型检测错误。

debug历程如下:
1.删掉el-form-item组件的prop属性,则不会报错。是表单数据对象检测错误。
2.去掉值表单验证也不会报错,证明数据对象检测在表单验证阶段出错。
3.确定自己定义的表单初始值类型并没有什么问题。
4.最后锁定问题出现在打开弹窗之后的重置表单方法resetFields
5.在resetFields方法之后输出表单数据,图如下

clipboard.png

初始定义的表单数据如下

clipboard.png

求各位大佬能给我解解惑,为啥我定义的text值为String,在经过表单之后会变为Array,究竟是我使用不规范还是ElementUI本身就认为textarea的字段值为Array亦或是其他问题,望指正。小弟在此拜谢各位大佬!

阅读 4.7k
2 个回答

v-if/v-else改成v-show

https://jsfiddle.net/s5ar1un3...

我也才发现有这种坑,原理后面慢慢说。

以下为element-ui源码

form.vue

//created阶段
created() {
      this.$on('el.form.addField', (field) => {
        if (field) {
          this.fields.push(field);
        }
      }
      
      this.$on('el.form.removeField', (field) => {
        if (field.prop) {
          this.fields.splice(this.fields.indexOf(field), 1);
        }

      });
}

//重置方法
resetFields() {
        ...
        
        this.fields.forEach(field => {
          field.resetField();
        });
      },

form-item.vue

//mounted阶段
    mounted() {
        if (this.prop) {
            this.dispatch('ElForm', 'el.form.addField', [this]);
            ...
            
            let initialValue = this.fieldValue;
            
            ...
            
            Object.defineProperty(this, 'initialValue', {
              value: initialValue
            });
            ...
        }
    }

//重置方法
    resetField() {

        this.validateDisabled = true;
        if (Array.isArray(value)) {
          prop.o[prop.k] = [].concat(this.initialValue);
        } else {
          prop.o[prop.k] = this.initialValue;
        }
    }

从源码可以看出,在form-itemmounted阶段确定每个field的初始值。

创建阶段

由于v-if/v-else,因此text所在组件创建没有prop(这时候创建了3个item组件,class本身也没写prop情况会一样),因此只有value所在item组件创建的时候触发了form组件的el.form.addField事件。所以保存的fields长度为1,仅保存了value这个组件。

选中之后
由于数据变动,vue比较差异更新,然后更新了第2个组件(这是重点),然鹅this.initialValueelement-ui定义的值,还是原来value的值。

结果就是
resetField的时候value.initialValue的值赋给了text

结论

所以resetField千万别用v-if/v-show除非你确认过上面的逻辑跟你的业务逻辑不冲突。
测试版本element-ui@2.3.3

补充

以上都是不看文档的结果(花式打脸),vue提供了:key阻止组件复用。Vuejs

Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染。这么做除了使 Vue 变得非常快之外,还有其它一些好处。例如,如果你允许用户在不同的登录方式之间切换

这样也不总是符合实际需求,所以 Vue 为你提供了一种方式来表达“这两个元素是完全独立的,不要复用它们”。只需添加一个具有唯一值的 key 属性即可:

注意如validate组件不存在不验证一样,resetField也不reset

被题主坑死,不按你debug历程,这问题早解决了。。话不多说,上代码:

    <template>
    <div>
        <el-button @click="onSelect">点我</el-button>

        <el-dialog
                title="弹窗"
                :visible.sync="dialogVisible"
                width="50%">
            <el-form
                    ref="formData"
                    :model="formData"
                    :rules="formRules">
                <el-form-item label="类型">
                    <el-select v-model="formData.Type">
                        <el-option label="类型1" value="class1"></el-option>
                        <el-option label="类型2" value="class2"></el-option>
                    </el-select>
                </el-form-item>
                <div v-if="formData.Type=='class1'">
                    <el-form-item label="class1" prop="desc">
                        <el-input type="textarea" v-model="formData.desc"></el-input>
                    </el-form-item>
                </div>
                <div v-else>
                    <el-form-item label="类型2" prop="region">
                        <el-select v-model="formData.region" multiple>
                            <el-option label="sdf" value="11"></el-option>
                            <el-option label="dsaf" value="22"></el-option>
                            <el-option label="dyrt" value="33"></el-option>
                        </el-select>
                    </el-form-item>
                </div>
                <el-form-item>
                    <el-button type="primary" @click="onSubmit('formData')">ok</el-button>
                </el-form-item>
            </el-form>
        </el-dialog>
    </div>
</template>

<script>
    export default {
        data(){
            return {
                dialogVisible: false,
                formData: {
                    Type: 'class1',
                    desc: '',
                    region: []
                },
                formRules: {
                    desc: [
                        {
                            required:true,
                            validator(rule, value, callback){
                                var Reg = /[\w+\/]*\w+/;
                                if(value==''){
                                    callback(new Error('该字段不能为空'))
                                }else if(!value.match(Reg) || value.match(Reg)!=value){
                                    callback(new Error('请输入正确的用户名,用户名之间用/隔开'));
                                }else{
                                    callback();
                                }
                            }
                        }
                    ],
                    region: [
                        { required:true, message:'不能为空', trigger: 'change' }
                    ]
                }
            }
        },
        methods: {
            onSubmit(formName){
                this.$refs[formName].validate((valid) => {
                    if (valid) {
                        this.dialogVisible = false;
                        alert('submit!');
                    } else {
                        console.log('error submit!!');
                        return false;
                    }
                });
            },
            onSelect(){
                this.dialogVisible = true;
                this.$nextTick(() => {
                    this.$refs.formData.resetFields();
                    console.log(this.Type)
                });
                console.log(this.formData)
            }
        }
    }
</script>

你的问题就是v-if/v-else这里,因为你初始Type为' ',v-if="formData.Type=='class1'",所以你才显示的是textarea这块。但是你第二次点击,执行resetFields后,Type为undefined,显示却为select这块,desc的类型会因为重置变的跟region一样,为Array;这也解释了为什么你第一次选择类型2,第二次点击进来,不会报错。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题