Vue中v-if在iView与ElementUI之间的表单校验差异

216 阅读4分钟

前言

Vue项目开发中,针对复杂表单,特别是根据不同条件渲染不同表单时,我们多半会使用 v-if 进行条件渲染,但是经常会遇到表单的校验问题,下面以 iViewview-designiView 的第四个版本,官方改名)代码举个例子:

// package.json 依赖版本
{
  "dependencies": {
    "vue": "^2.6.14",
    "view-design": "^4.7.0"
  }
}
// .vue 文件
<template>
  <div>
    <Form :model="form">
      <FormItem label="选择渲染不同组件" prop="componentType">
        <RadioGroup v-model="form.componentType">
          <Radio label="1">组件1</Radio>
          <Radio label="2">组件2</Radio>
        </RadioGroup>
      </FormItem>
      <template v-if="form.componentType === '1'">
        <FormItem label="我是组件1" prop="inputValue1">
          <Input
            v-model="form.inputValue1"
            placeholder="我是组件1"
          ></Input>
        </FormItem>
      </template>
      <template v-if="form.componentType === '2'">
        <FormItem label="我是组件2" prop="inputValue2">
          <Input
            v-model="form.inputValue2"
            placeholder="我是组件2"
          ></Input>
        </FormItem>
      </template>
    </Form>
  </div>
</template>
<script>
  export default {
    data() {
      return {
        form: {
          componentType: "",
          inputValue1: "111111",
          inputValue2: "222222",
        }
      }
    }
  }
</script>

示例效果:

file.gif

问题集合

一、添加校验属性 required

假设当选择组件1时,inputValue1 必填,那么就在组件1上增加 required 属性,代码如下:

/// 其他代码
      <template v-if="form.componentType === '1'">
        // 注意下面这一行,增加了属性 required
        <FormItem label="我是组件1" prop="inputValue1" required>
          <Input
            v-model="form.inputValue1"
            placeholder="我是组件1"
          ></Input>
        </FormItem>
      </template>
/// 其他代码

示例效果:

先选组件1

newfile.gif

先选组件2

222.gif

我们会发现两种情况:

1、如果先选择了组件1,再选择组件2,表现异常,inputValue2 的值被赋值为 inputValue1 的值

打开 Vue DevTools 面板可以查看到数据已经被改变了

image.png

2、如果先选择组件2,再选择组件1,则表现正常

二、原因

我们知道 vue 本身的 diff 算法是复用机制,第一次渲染了 FormItem ,后续切换内部实际上复用了上一次的,只是属性的正常替换,如果不想复用,需要添加不同的 key 属性,官方文档

组件本身复用好理解,但从上面的表现来看,先后的选择引起的复用,却是不同的表现,那么差异点只是在组件1多了 required 属性,那么我们进入 view-design 的源码查看,可以看到 watch 中对 required 属性实现了监听。

image.png

简单来说就是 required 属性 从 truefalse,会执行一个回调事件 resetField,该事件的主要作用,是重置该表单项的校验,并给绑定的 prop 属性重新赋值 initialValue

image.png

然后 initialValue 是在组件 mounted 中被赋值为 fieldValuefieldValue 是组件对应的实时的值

image.png

那么结合上面的例子,流程就是:

// 先选择组件1,第一次渲染 FormItem
{
    prop: "inputValue1",
    fieldValue: "111111",
    initialValue: "111111",
    required: true,
}
// 切换到组件2,复用 FormItem
// 因为生命周期 mounted 只执行一次,所以 initialValue 赋值后就不再改变
{
    prop: "inputValue2",
    fieldValue: "222222",
    initialValue: "111111",
    required: false,
}
// 此时触发了 required 的 watch 监听,执行了 resetField 函数,
// 将 initialValue 赋值给了对应的 prop 属性字段,结果就是
{
    prop: "inputValue2",
    fieldValue: "111111",
    initialValue: "111111",
    required: false,
}
// 表现异常,出现 bug

// ------------------分割线------------------

// 先选择组件2,第一次渲染 FormItem
{
    prop: "inputValue2",
    fieldValue: "222222",
    initialValue: "222222",
    required: false,
}
// 切换到组件1,复用 FormItem
// 因为生命周期 mounted 只执行一次,所以 initialValue 赋值后就不再改变
// 此时触发了 required 的 watch 监听,但判断为 false,不会执行了 resetField 函数
{
    prop: "inputValue1",
    fieldValue: "111111",
    initialValue: "222222",
    required: true,
}
// 再切换到组件2,继续复用 FormItem
// 此时触发了 required 的 watch 监听,执行了 resetField 函数,
// 将 initialValue 赋值给了对应的 prop 属性字段,结果就是
{
    prop: "inputValue2",
    fieldValue: "222222",
    initialValue: "222222",
    required: false,
}
// 表现正常

iView 出现这个问题,那我们继续对 element-ui 进行同样的测试,结果却发现并没有这样的问题。这里就不贴代码了,感兴趣的伙伴可以自行验证。

"element-ui": "^2.15.6"

然后 required 属性会出现问题,那我们采用 rules 属性是否会出现类似的问题呢,答案是不会出现。

综上所述,这是 view-design 的 bug。

三、解决方案

1、给表单项增加不同的属性 key,既然组件复用会导致这个问题,那我们直接不复用组件,就能从根源解决问题了,同时这也是解决表单校验的终极解决方案;对 iViewElementUI 都适用。(推荐使用)

2、不用 required 属性,而改用 rules 属性,这个就不会引起重置的回调,也能解决上述问题,但是不能解决表单校验的问题,如果规则没有加入条件判断,那么规则也会被加入校验数组中,这个在源码中有个 fields 字段记录了校验方法,会导致提交的时候,无法通过校验。页面上可能没有校验提示,遇到这种情况,可以在表单的 validate 回调中打印错误信息就知道了。(一般推荐)

// 其他代码
      <template v-if="form.componentType === '1'">
        <FormItem 
          label="我是组件1"
          prop="inputValue1"
          :rules="
            form.componentType === '1' 
              ? { required: true } 
              : {}
          " // <- 注意这一行rules 里面也增加了条件判断
        >
          <Input
            v-model="form.inputValue1"
            placeholder="我是组件1"
          ></Input>
        </FormItem>
      </template>
// 其他代码

3、用 v-show 替换 v-ifv-show 实际上组件已经渲染,只是不显示,也做到了不复用,但是也会存在和第2点一样的问题,就是如果先选择了组件1,然后切换到组件2,在最后提交的时候,会发现无法提交数据,页面也没展示校验报错,原因是:尽管组件的 v-showfalse,但实际上还是参与了校验,只是页面上没有显示,所以提交不了。(不推荐)