写在前面

  • 在日常开发中,我们常常会通过ref=变量名 的方式获取组件的实例对象;
  • 用得最多的莫过于form表单了,本文基于Vue3+TypeScript+ElementPlus的例子介绍一下为多个表单设置ref属性;

常规用法

  • 对于单个的表单,我们通过ref=属性名的方式就能够轻松获取到表单实例对象,从而使用该对象上的校验表单项的方法,就像下面这样:
  • 自定义变量ruleFormRef赋值给 ref 属性,通过ruleFormRef.value 拿到form表单的实例对象,通过调用validate方法对表单项目进行校验;
    在这里插入图片描述

问题

  • 但是当我们的form表单是动态生成并且存在多个时,这种ref=变量名方式就出问题了;
  • 例如下面的例子,项目可以动态添加多个;但是每个项目的内容是相同的,同时每个项目的必填项都需要填写,这时候我们希望在提交表单的时候通过 validate 方法校验所有项目;于是想当然的和上面例子一样的写法:
    在这里插入图片描述
  • 通过for循环遍历数组,动态生成多个form表单,通过ref拿到表单实例调用validate从而触发表单校验;
  • 但是实际点击提交之后却没有任何反应,断点调试之后发现实例竟是undefined;
    在这里插入图片描述

问题产生原因以及解决方式

  • 其实仔细想问题就出在于我们的表单是for循环动态生成的,但是ref绑定的始终是一个变量,那么当点击提交按钮时;程序也肯定无法判断当前ref绑定的是哪一个form的实例对象;
  • 因此解决关键点就是需要给每一个form表单绑定独立的ref属性,所以ref就不能定义为简单的变量了,需要是一个对象或者数组,用来存储所有表单的实例对象,然后在点击提交按钮时遍历所有的实例对象并且逐个调用validate 方法,从而实现每个表单的独立校验;
  • 那么这里我以数组举例,写法如下:
    在这里插入图片描述
  • 修改之后,我们再进浏览器,点击提交查看效果,从下图可以看到已经正常校验了;
  • 完整代码例子
<template>
  <div style="margin: 20px">
    <el-button type="primary" size="default" @click="handleAdd">新增</el-button>
    <el-button type="primary" @click="submitForm">提交</el-button>
  </div>
  <div v-for="(ruleForm, index) in state.list" :key="index">
    <el-form
      :ref="(el: FormInstance) => (ruleFormRefs[index] = el)"
      :model="ruleForm"
      :rules="rules"
      label-width="120px"
      class="demo-ruleForm"
      :size="formSize"
    >
      <el-form-item label="名称" prop="name">
        <el-input v-model="ruleForm.name" />
      </el-form-item>
      <el-form-item label="地区" prop="region">
        <el-select v-model="ruleForm.region" placeholder="Activity zone">
          <el-option label="Zone one" value="shanghai" />
          <el-option label="Zone two" value="beijing" />
        </el-select>
      </el-form-item>
      <el-form-item label="地区2" prop="count">
        <el-select-v2 v-model="ruleForm.count" placeholder="Activity count" :options="options" />
      </el-form-item>
      <el-form-item label="时间" required>
        <el-col :span="11">
          <el-form-item prop="date1">
            <el-date-picker
              v-model="ruleForm.date1"
              type="date"
              label="Pick a date"
              placeholder="Pick a date"
              style="width: 100%"
            />
          </el-form-item>
        </el-col>
      </el-form-item>
    </el-form>
    <el-divider direction="horizontal" content-position="left"></el-divider>
  </div>
</template>

<script lang="ts" setup>
import { reactive, ref } from 'vue';
import type { FormInstance, FormRules } from 'element-plus';

interface RuleForm {
  name: string;
  region: string;
  count: string;
  date1: string;
}

const formSize = ref('default');
// const ruleFormRef = ref<FormInstance>();
const ruleFormRefs = ref<FormInstance[]>([]);
const state = reactive<{
  list: RuleForm[];
}>({
  list: [
    {
      name: '',
      region: '',
      count: '',
      date1: '',
    },
  ],
});

const rules = reactive({
  name: [
    { required: true, message: 'Please input Activity name', trigger: 'blur' },
    { min: 3, max: 5, message: 'Length should be 3 to 5', trigger: 'blur' },
  ],
  region: [
    {
      required: true,
      message: 'Please select Activity zone',
      trigger: 'change',
    },
  ],
  count: [
    {
      required: true,
      message: 'Please select Activity count',
      trigger: 'change',
    },
  ],
  date1: [
    {
      type: 'date',
      required: true,
      message: 'Please pick a date',
      trigger: 'change',
    },
  ],
});

const handleAdd = () => {
  state.list.push({
    name: '',
    region: '',
    count: '',
    date1: '',
  });
};

const submitForm = async () => {
  ruleFormRefs.value?.forEach((ele) => {
    ele.validate((val) => {
      console.log(val);
    });
  });
  // await ruleFormRef.value?.validate((valid, fields) => {
  //   if (valid) {
  //     console.log('submit!');
  //   } else {
  //     console.log('error submit!', fields);
  //   }
  // });
};

// const resetForm = (formEl: FormInstance | undefined) => {
//   if (!formEl) return;
//   formEl.resetFields();
// };

const options = Array.from({ length: 10000 }).map((_, idx) => ({
  value: `${idx + 1}`,
  label: `${idx + 1}`,
}));
</script>

写在最后

  • 另外值得注意的是,以上例子只是一个简单的测试用例,细节的地方没有过多处理;
  • 如果在实际开发中,这种新增往往伴随着删除,修改等操作;用数组下标作为for循环的key值不可取,应该使用其他唯一标识的字段;同时用于存储form实例的缓存可以定义为对象,通过key-value的形式进行存储,当然如果每个项目的key值刚好是number类型的话,定义成数组就行。
Logo

前往低代码交流专区

更多推荐