对ElementUI表单验证的二次封装

ElementUI是我用得很多的一套UI库,而且做前端么,不可避免地要写一些表单;虽然有人说表单会让用户厌烦,但是很多场景就是要让用户填表单。

写了表单就要写表单验证,但ElementUI的表单验证并不是特别友好,会出现大量的逻辑冗余。比如,如果我要验证结束时间不能早于开始时间,同时结束日期不能早于开始日期,需要写两遍极其相似的逻辑:

var validateTime = (rule, value, callback) => {
    if (value === '') {
        callback(new Error('结束时间不能为空'));
    } else if (value <= this.form.startTime) {
        callback(new Error('结束时间不能早于开始时间'));
    } else {
        callback();
    }
};

var validateDate = (rule, value, callback) => {
    if (value === '') {
        callback(new Error('结束日期不能为空'));
    } else if (value <= this.form.startDate) {
        callback(new Error('结束日期不能早于开始日期'));
    } else {
        callback();
    }
};

可以看到,这里的表单验证逻辑完全相同,只是换了一下验证的数据和提示语。可以想象,如果有多个表单,并且每个表单里都有需要验证的日期和时间,会有大量冗余(事实上这次的业务场景就是这样)。

解决这个问题,主要是对其中的变化点进行封装(EVP),可以抽象出这样一个函数:

function endTimeValidator (
  startTime,
  noValueMsg = '请指定结束时间',
  beforeStartTimeMsg = '结束时间必须晚于开始时间',
  required = true
) {
  return (rule, value, callback) => {
    if (value && value <= startTime) {
      callback(new Error(beforeStartTimeMsg))
    } else {
      if (required && !value) {
        callback(new Error(noValueMsg))
      } else {
        callback()
      }
    }
  }
}

其中required用于指定是否是必选。在具体使用的时候,可以这样:

endTimeValidator(
    this.courseAdditionData.courseStartTime,
    '请选择课程结束时间',
    '课程结束时间必须晚于开始时间',
    false
)

但是这个函数存在两个问题:

  1. Vue的生命周期,在组件created之前data没有初始化,如果直接在data中声明(就像官方文档的用法一样),会报错undefined;
  2. 虽然Vue的data是可响应的,但函数本身不是,可以看到,调用的时候传入的是data在初始化的时候的快照,这就导致了函数里获取到的事实上一直是courseStartTime的初始值。

为了解决第一个问题,可以把这个函数放到mounted里。但是第二个问题呢?这个时候我们可以想到JS的一个语言特性:闭包。所以可以对函数进行改造:

function endTimeValidator (
  startTime, // startTime的类型是函数,() => string
  noValueMsg = '请指定结束时间',
  beforeStartTimeMsg = '结束时间必须晚于开始时间',
  required = true
) {
  return (rule, value, callback) => {
    if (value && value <= startTime()) {
      callback(new Error(beforeStartTimeMsg))
    } else {
      if (required && !value) {
        callback(new Error(noValueMsg))
      } else {
        callback()
      }
    }
  }
}

这样,利用函数闭包,实现了类似于late binding的效果。在使用的时候可以这样:

endTimeValidator(
    () => this.courseAdditionData.courseStartTime,
    '请选择课程结束时间',
    '课程结束时间必须晚于开始时间',
    false
)

思路主要来源于Vue 3的函数式API。在早期(还是vue-composition-api的时候),实现大概是这样:

interface Option<T> {
    get: () => T;
    set: (value: T) => void;
}
export declare function computed<T>(getter: Option<T>['get']): Ref<T>;

也是利用函数闭包来实现的。虽然现在已经是alpha了,但是其实这一块的核心逻辑变化并不大。

再多想一点,从某个层面来说,这个其实和代理模式(proxy pattern)是有点像的。这个函数闭包是作为真实数据的一个代理而存在的,用来解决直接访问数据而导致的不能响应的问题;当然如果直接当成是一个委托关系似乎也没什么问题。说白了,还是为了解耦,让逻辑不要和数据耦合在一起。

发布了110 篇原创文章 · 获赞 132 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/HermitSun/article/details/104102883