el-table通过这样封装可以实现校验-表格校验的原理

我们一般在后台系统中,很常见的操作时表格里面嵌套表单,之前我的网上找到了一些封装的用法:

js 复制代码
<el-form :model="formData" :rules="ruleData" ref="formDom">
  <el-table :data="formData.tableData">
    <el-table-column
      v-for="item in column"
      :key="item.prop"
      :label="item.label"
    >
      <template slot-scope="scope">
        <el-form-item
          :ref="'tableData.' + scope.$index + '.' + item.prop"
          :prop="'tableData.' + scope.$index + '.' + item.prop"
          :rules="ruleData[item.prop]"
        >
          <el-input
            v-model="scope.row[item.prop]"
            @change="handleChange(scope, item)"
          ></el-input>
        </el-form-item>
      </template>
    </el-table-column>
  </el-table>
</el-form>

// data中的数据
formData: {
  tableData: [
    { name: "", age: "" },
    { name: "", age: "" },
    { name: "", age: "" },
    { name: "", age: "" },
  ],
},
ruleData: {
  name: { message: "请输入名字", required: true },
  age: { message: "请输入年龄", required: true },
},
column: [
  { label: "名字", prop: "name" },
  { label: "年龄", prop: "age" },
],

在这里我不太理解prop为什么要写成"'tableData.' + scope.$index + '.' + item.prop"这个样子就实现了校验的效果。后来在一次项目中偶然去查看了一下el-form的源码,才明白其中的道理;

首先我们看到el-form组件的文件路径是这样的,那么其实form-item是一个单独的组件,我们通过form -> form-item嵌套的时候,其实最后实现校验的过程是一个个去校验 form-item,form组件最终的校验:

js 复制代码
// 最主要的核心功能
validate(callback){
  this.fields.forEach(field => {
  field.validate('', (message, field) => {
    if (message) {
      valid = false;
    }
    invalidFields = objectAssign({}, invalidFields, field);
    if (typeof callback === 'function' && ++count === this.fields.length) {
      callback(valid, invalidFields);
    }
  });
});
}

其中 this.fields就是el-form-item集合:

js 复制代码
// el-form的created中
this.$on('el.form.addField', (field) => {
  if (field) {
    this.fields.push(field);
  }
});

// el-form-item的mounted中
this.dispatch('ElForm', 'el.form.addField', [this]);

// 内部自己使用 dispatch实现组件通讯

其中最主要的就是调用el-form-itemvalidate方法:

js 复制代码
validate(trigger, callback = noop) {
        this.validateDisabled = false;
        const rules = this.getFilteredRule(trigger);
        if ((!rules || rules.length === 0) && this.required === undefined) {
          callback();
          return true;
        }

        this.validateState = 'validating';

        const descriptor = {};
        if (rules && rules.length > 0) {
          rules.forEach(rule => {
            delete rule.trigger;
          });
        }
        descriptor[this.prop] = rules;

        const validator = new AsyncValidator(descriptor);
        const model = {};

        model[this.prop] = this.fieldValue;

        validator.validate(model, { firstFields: true }, (errors, invalidFields) => {
          this.validateState = !errors ? 'success' : 'error';
          this.validateMessage = errors ? errors[0].message : '';

          callback(this.validateMessage, invalidFields);
          this.elForm && this.elForm.$emit('validate', this.prop, !errors, this.validateMessage || null);
        });
      }

其中主要的核心功能是单个的 rules(校验规则)model(数据)

rules的获取:

js 复制代码
// 首先通过getFilteredRule方法过滤rules
getFilteredRule(trigger) {
  // 获取 rules
  const rules = this.getRules();
  return rules.filter(rule => {
    if (!rule.trigger || trigger === '') return true;
    if (Array.isArray(rule.trigger)) {
      return rule.trigger.indexOf(trigger) > -1;
    } else {
      return rule.trigger === trigger;
    }
  }).map(rule => objectAssign({}, rule));
},

// getFilteredRule最核心的方式就是getRules
getRules() {
  // 这个就是我们在 el-form中传递的rules
  let formRules = this.form.rules;
  // 这个就是我们自己在 el-form-item传递的rules
  const selfRules = this.rules;
  // 这里判断是不是必输的
  const requiredRule = this.required !== undefined ? { required: !!this.required } : [];
  // 这里通过formRules结合prop获取最新的prop(其实是一个对象,里面有key,value比较重要的值)
  const prop = getPropByPath(formRules, this.prop || '');
  formRules = formRules ? (prop.o[this.prop || ''] || prop.v) : [];
  // 最终将rules做一个整合
  return [].concat(selfRules || formRules || []).concat(requiredRule);
},

最终我们通过分析发现其实去匹配 el-form中整体的rules是通过getPropByPath这个方法的:

js 复制代码
function getPropByPath(obj, path) {
  let tempObj = obj;
  path = path.replace(/\[(\w+)\]/g, '.$1');
  path = path.replace(/^\./, '');
  let keyArr = path.split('.');
  let i = 0;
  for (let len = keyArr.length; i < len - 1; ++i) {
    let key = keyArr[i];
    if (key in tempObj) {
      tempObj = tempObj[key];
    } else {
      throw new Error('please transfer a valid prop path to form item!');
    }
  }
  return {
    o: tempObj,
    k: keyArr[i],
    v: tempObj[keyArr[i]]
  };
}

path.split('.')这里会对prop进行切割,我们最终得到的值其实是这样的:

最终通过formRules = formRules ? (prop.o[this.prop || ''] || prop.v) : [];formRules这个值变成了undefined

再返回validate方法里面,看model,通过这里model[this.prop] = this.fieldValue,我们来看fieldValue

js 复制代码
function getPropByPath(obj, path) {
  // 这里的值其实一个对象数组:
  // { tableData: [
  // { name: "", age: "" },
  // { name: "", age: "" },
  // { name: "", age: "" },
  // { name: "", age: "" },
// ] },
  let tempObj = obj;
  path = path.replace(/\[(\w+)\]/g, '.$1');
  path = path.replace(/^\./, '');
  // 那第一个来说就是 tableData.0.name
  let keyArr = path.split('.');
  let i = 0;
  for (let len = keyArr.length; i < len - 1; ++i) {
    // 不断的去匹配 tempObj 中的值
    let key = keyArr[i];
    if (key in tempObj) {
      tempObj = tempObj[key];
    } else {
      throw new Error('please transfer a valid prop path to form item!');
    }
  }
  return {
    o: tempObj,
    k: keyArr[i],
    v: tempObj[keyArr[i]]
  };
}
// 其实就是一个计算属性
fieldValue() {
  // 我们给 el-form传递的model值
  const model = this.form.model;
  if (!model || !this.prop) { return; }
  let path = this.prop;
  if (path.indexOf(':') !== -1) {
    path = path.replace(/:/, '.');
  }
  // 还是通过这个方法获取到对应的值
  return getPropByPath(model, path, true).v;
},

最终fieldValue就表示了表格组件中每个小块的值,通过 propmodel去匹配对应的值,和rules结合AsyncValidator实现表单校验。这里我们也可以看出给el-form传递model,给el-form-item传递proprules的重要性。

写到这里我还有一个疑问,那就是在getRules方法中,通过getPropByPath其实最终是把formRules转换成一个undefined的,那么是不是在此次封装中给el-form传递的rules没用了,其实真正起主导作用的还是给el-form-item传递的rules,那么把el-form-item中的rules删除掉是不是就校验不了了。通过验证证实了我的猜想。

相关推荐
M_emory_13 分钟前
解决 git clone 出现:Failed to connect to 127.0.0.1 port 1080: Connection refused 错误
前端·vue.js·git
Ciito16 分钟前
vue项目使用eslint+prettier管理项目格式化
前端·javascript·vue.js
成都被卷死的程序员1 小时前
响应式网页设计--html
前端·html
fighting ~1 小时前
react17安装html-react-parser运行报错记录
javascript·react.js·html
老码沉思录1 小时前
React Native 全栈开发实战班 - 列表与滚动视图
javascript·react native·react.js
abments1 小时前
JavaScript逆向爬虫教程-------基础篇之常用的编码与加密介绍(python和js实现)
javascript·爬虫·python
mon_star°1 小时前
将答题成绩排行榜数据通过前端生成excel的方式实现导出下载功能
前端·excel
Zrf21913184551 小时前
前端笔试中oj算法题的解法模版
前端·readline·oj算法
老码沉思录1 小时前
React Native 全栈开发实战班 - 状态管理入门(Context API)
javascript·react native·react.js
文军的烹饪实验室2 小时前
ValueError: Circular reference detected
开发语言·前端·javascript