复杂表单验证、提交方案(设计模式之策略模式)

1,924 阅读2分钟

在一个Web项目中,我们常常会遇到表单提交的需求,当表单项过多时,我们普通的逻辑的判断就会增加额外的if else判断使项目非常臃肿,接下来我总结近期遇到的表单提交的需求。

策略模式介绍

现实生活举例: 比如我们要去某个地方旅游,可以根据具体的实际情况来选择出行的线路。

  • 如果没有时间但是不在乎钱,可以选择飞机;
  • 如果没有钱,可以选择大巴或者火车;
  • 如果再穷一点,可以选择飞骑自行车;

一个功能有多种方案实现,策略模式就是把他们一个个封装起来,并且可以达到相互替换。

接下来我们说表单验证,假如我们有一个表单,校验逻辑如下:

  • 用户名不为空
  • 密码长度不低于6位
  • 手机号符合格式
 const registerForm = document.getElementById('registerForm');
    registerForm.onsubmit = function() {
        if (registerForm.userName != "") {
            if (registerForm.password.length >= 6) {
                if (/^1[3|4|5|7|8][0-9]{9}$/.test(registerForm.phoneNumber)) {
                    alert('成功');
                } else {
                    alert('请输入正确的的手机号!');
                    return false;
                }
            } else {
                alert('密码不能小于六位');
                return false;
            }
        } else {
            alert("用户名不能为空!");
            return false;
        }
    }
}

很明显的缺点就是,registerForm.onsubmit函数比较庞大,包含了许多了if-else语句,这些语句需要覆盖所有的校验规则,如果我们要增加一种校验,就必须进入提交函数内部去更改,复用性非常差。

重构表单验证

接下来是我最近做的需求利用策略模式去改进,首先拿到一份表单后进行组件拆分


    <div class="fr-title">基础信息</div>

    <div class="fr__section">
      <!-- 券ID -->
      <CouponId :couponId="actId" v-if="actId" :isModify="isModify"></CouponId>

      <!-- 券名称(商家见) -->
      <NameComponent ref="refName"></NameComponent>

      <!-- 基础信息 -->
      <BaseInfoEditor ref="refBaseInfo"></BaseInfoEditor>
    </div>
    <!-- 商品选择 -->
    <ItemManage ref="refItemManage"></ItemManage>

    <div class="fr__section">
      <!-- 助力人数&&次数 -->
      <Assistance ref="refAssistance"></Assistance>
    </div>

    <div class="fr__section">
      <!-- 助力次数 -->
      <Participate ref="refParticipate"></Participate>
    </div>

    <!-- 优惠券选择 -->
    <CouponGive ref="refCouponGive"></CouponGive>

      <!-- 占位 -->
    <div class="mkt-area-inset-bottom"></div>

    <!-- 添加按钮 -->
    <div class="mkt-footer">
      <div class="mkt-footer-c">
        <div class="mkt-area-inset-bottom">
          <a class="mkt-footer-btn" @click="handleSave">保存</a>
        </div>
      </div>
    </div>
  </section>

子组件

把每个组件拆分到位后,在每个子组件中去暴露_setData、_getData、_verify这三个方法,供父组件去调用,

//数据回填用
$_setData(_data) {
  this.remarks = _data.remarks;
  this.cacheRemarks = this.remarks;
},
//提交数据用
$_getData() {
  return {
    remarks: this.remarks,
  }
},
//校验用
$_verify() {
  let res = this._checkRemarks();
  return res;
},

//具体校验规则
_checkRemarks() {
  let message = "";
  if (!this.remarks) {
    message = "请输入券名称";
  } else if (getTextLength(this.remarks) > MAX_LENGTH) {
    message = `券名称不能超过${MAX_LENGTH}个字`;
  }
  return { status: !message, message }
}

父组件

父组件可以通过this.$refs去调用每个子组件的内部方法,在父组件中去进行存值、取值、校验,点击保存按钮后去调用handleSave()方法进行提交。


// 创建需要的参数
_getParamsByCreate() {
// 券名称处理
Object.assign(formData,dataProcessing.nameTransferForInf(this.$refs.refName.$_getData()));
}

// 统一校验处理
_checkAll() {
  //判断是编辑态还是创建态
  let checkArr =  this.isModify ? VALIDATE_ARR_MODIFY : VALIDATE_ARR_CREATE;
  let res = { status: true, message: "" };
  checkArr.forEach(name => {
    if(res.status && this.$refs[name]) {
      res = this.$refs[name].$_verify();
    }
  });
  
// 保存
async handleSave() {
    // 进行校验
    let checkRes = this._checkAll();
    if (!checkRes.status) {
      this.$$toast({ content: checkRes.message });
      return false;
    }
    this._createAssistCoupon();
  }
},

如果表单修改时,此时我们需要进行数据的回填,这个时候子组件的_setData方法派上用场,

//券名称回填 

this.$refs.refName.$_setData(dataProcessing.nameTransferForComponent(assistInfo));

我遇到的一个场景是,当填写完一些数据后,走到商品选择那里,跳页去勾选一些商品回来的时候,这时会发现之前填的数据都不见了,打印一下this发现没有这些数据了,我的解决方案是在跳转商品选择页之前父组件先去调用每一个子组件暴露的_getData(),把这些数据都放在this上,跳页回来后再通Vue中的props去动态传参给每个子组件数据,在每个子组件的created钩子函数中去进行一次复值,关于数据流的走向一定要清楚父子组件的生命周期。避免数据在传递过程中发生丢失。

父子组件的生命周期

父beforeCreate-> 父create -> 子beforeCreate-> 子created -> 子mounted -> 父mounted 子组件挂载完成后,父组件还未挂载。所以组件数据回显的时候,在父组件mounted中获取api的数据,子组件的mounted是拿不到的。