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删除掉是不是就校验不了了。通过验证证实了我的猜想。

相关推荐
糕冷小美n11 小时前
elementuivue2表格不覆盖整个表格添加固定属性
前端·javascript·elementui
小哥不太逍遥11 小时前
Technical Report 2024
java·服务器·前端
沐墨染11 小时前
黑词分析与可疑对话挖掘组件的设计与实现
前端·elementui·数据挖掘·数据分析·vue·visual studio code
anOnion11 小时前
构建无障碍组件之Disclosure Pattern
前端·html·交互设计
threerocks11 小时前
前端将死,Agent 永生
前端·人工智能·ai编程
问道飞鱼12 小时前
【前端知识】Vite用法从入门到实战
前端·vite·项目构建
爱上妖精的尾巴12 小时前
8-10 WPS JSA 正则表达式:贪婪匹配
服务器·前端·javascript·正则表达式·wps·jsa
Zhencode12 小时前
Vue3响应式原理之ref篇
vue.js
shadow fish13 小时前
react学习记录(三)
javascript·学习·react.js
小疙瘩13 小时前
element-ui 中 el-upload 多文件一次性上传的实现
javascript·vue.js·ui