如何让自定义的组件优雅的触发el-form的校验

背景

最近自己封装了一个树节点选择组件,在对其做优化的时候想着能不能像el-input那样触发el-form的校验,并将错误提示显示在对应的el-form-item下,如图。

在这里插入图片描述

分析

为了实现这一效果,首先可以el-input为例,参考它的源码是怎么实现的。以下是el-input的部分源码,截取了主要的相关逻辑部分,非完整源码

源码路径:element-ui/packages/input/src/input.vue

<template>
  <div>
    <template v-if="type !== 'textarea'">
      <input
        ref="input"
        @input="handleInput"
        @blur="handleBlur"
        @change="handleChange"
      >
    </template>
  </div>
</template>
<script>
  import emitter from 'element-ui/src/mixins/emitter';

  export default {
    mixins: [emitter],
    
    data() {
      return {
        textareaCalcStyle: {},
        hovering: false,
        focused: false,
        isComposing: false,
        passwordVisible: false
      };
    },

    props: {
      value: [String, Number],
    },

    computed: {
    },

    watch: {
      value(val) {
        this.$nextTick(this.resizeTextarea);
        if (this.validateEvent) {
          // 在值改变的时候触发change校验  
          this.dispatch('ElFormItem', 'el.form.change', [val]);
        }
      },
    },

    methods: {
      handleBlur(event) {
        this.focused = false;
        this.$emit('blur', event);
        if (this.validateEvent) {
          // 在失去焦点的时候触发blur校验
          this.dispatch('ElFormItem', 'el.form.blur', [this.value]);
        }
      },
      handleInput(event) {
        this.$emit('input', event.target.value);
      },
      handleChange(event) {
        this.$emit('change', event.target.value);
      },
    }
  };
</script>

从上面的代码可以看出,el-input组件会在blurchange时调用dispatch方法,通过该方法触发相应的校验。而dispatch是ElementUI自己封装的通信组件Emitter中的重要组成部分(关于Emitter更多信息见下方文章末尾的拓展)。

实现

既然已经清楚了在el-input中是怎么样实现的了,那么剩下的就是照葫芦画瓢了:

  1. 混入或继承emitter,这里以混入为例:

    import emitter from 'element-ui/src/mixins/emitter';
    export default {
        mixins: [emitter]
    }
    
  2. 在你想要触发校验的时候调用dispatch,一般就是在changeblur的时候。

    //  evemtName: 事件名,'el.form.blur'或'el.form.change'
    //  params: 参数,一般主要就是值
    this.dispatch('ElFormItem', eventName, params);
    

    特别需要注意的是,params为一个数组,我们可以把所需的参数放在这个数组中,比如:[value, label…],这样在el-form-item就会触发对应emit并传出这些参数,比如:this.$emit('el.form.blur', value, label...)

  3. 最后就是设置form Rules,然后把自己的组件像el-input放到el-form-item中,这样就能看到我们想要的结果啦。

    <template>
      <el-form size="mini" :rules="rules" :model="record">
        <el-form-item prop="xxx">
          <TreeSelectv-model="record['xxx']"></TreeSelect>
        </el-form-item>
      </el-form>
    </template>
    
    <script>
    
    import TreeSelect from "@/components/Tree/TreeSelect.vue";
    
    export default {
      components: {TreeSelect},
      data() {
        return {
          record: {
            xxx: ["111"],
          },
          treeList: [...]
        };
      },
      computed: {
        rules() {
          return {
            xxx: [
                {required: true, type: "array", message: "xxx不能为空哦!", trigger: "change"}
            ],
          };
        }
      }
    };
    </script>
    

在这里插入图片描述

拓展

接下来聊聊Emitter,它是Element UI为了解决父子组件通信问题,避免频繁使用props和emit而自行封装的一个组件通信工具。主要分为两大核心:dispatch和broadcast。

dispatch用于向父组件逐级冒泡传递事件,主要就是通过while循环逐级向上查找父组件,直到找到对应的父组件,并让它通过emit触发指定事件,同时传出相应参数,而在对应的父组件也存在着对‘emit触发指定事件,同时传出相应参数,而在对应的父组件也存在着对`emit触发指定事件,同时传出相应参数,而在对应的父组件也存在着对emit事件的处理(也就是通过this.on‘监听∗∗同一个组件∗∗的‘this.on`监听**同一个组件**的`this.on监听同一个组件this.emit的自定义事件触发)。就比如el-form-item中就可能会在mounted的时候就监听el.form.blurel.form.change`的变化。

this.$on('el.form.blur', this.onFieldBlur);
this.$on('el.form.change', this.onFieldChange);

broadcast用于向子组件传递事件,主要通过递归的方式找到目标子组件,找到对应目标后和dispatch一样,同样通过this.$emit触发指定事件,同时传出相应参数,然后目标子组件通过this.$on进行监听和处理。关于broadcast的使用可以参考一下select.vueselect-dropdown.vue的通信。

select组件的路径:element-ui/packages/select/src/select.vue

感谢各位的阅读,如有缺漏或错误欢迎指正,同样欢饮留言探讨,最后,附上Emitter的源码:

function broadcast(componentName, eventName, params) {
  this.$children.forEach(child => {
    var name = child.$options.componentName;

    if (name === componentName) {
      child.$emit.apply(child, [eventName].concat(params));
    } else {
      broadcast.apply(child, [componentName, eventName].concat([params]));
    }
  });
}
export default {
  methods: {
    dispatch(componentName, eventName, params) {
      var parent = this.$parent || this.$root;
      var name = parent.$options.componentName;

      while (parent && (!name || name !== componentName)) {
        parent = parent.$parent;

        if (parent) {
          name = parent.$options.componentName;
        }
      }
      if (parent) {
        parent.$emit.apply(parent, [eventName].concat(params));
      }
    },
    broadcast(componentName, eventName, params) {
      broadcast.call(this, componentName, eventName, params);
    }
  }
};
Element UI中,如果你在一个`el-form`组件内部嵌套另一个`el-form`,并且想要进行整体的校验,可能会遇到一些问题。这是因为Element UI默认只对顶级`el-form`的表单数据进行验证。当你在子`el-form`中添加验证规则并提交,这些规则并不会影响到父级的验证状态。 要解决这个问题,你可以尝试以下几个步骤: 1. **手动触发验证**:在子`el-form`的提交事件中,手动调用`this.$refs.form.validate()`来触发整个表单的验证。 ```javascript <template> <div> <el-form ref="parentForm"> <!-- ... --> <el-form :ref="childForm" :model="childFormData"> <!-- ... --> <el-form-item v-if="shouldValidateChild"> <!--form验证项 --> </el-form-item> </el-form> </el-form> </div> </template> <script> export default { data() { return { childFormData: {}, shouldValidateChild: false, }; }, methods: { handleSubmit() { // 先验证子form this.shouldValidateChild = true; this.$nextTick(() => { if (this.childFormData.valid) { // 判断子form是否有效 this.$refs.parentForm.validate(); } }); }, }, }; </script> ``` 2. **自定义验证策略**:如果需要更复杂的验证逻辑,可以考虑将子form单独封装成一个组件,并提供一个方法供外部调用来验证整个表单结构。 3. **父子联动**:如果子form的验证结果对父form有直接影响,你可能需要通过props或者Vuex等状态管理工具来传递验证结果,并相应地更新父级的状态。 请注意,上述方法可能需要结合具体的项目需求进行调整。若还有其他疑问或遇到具体问题,请详细描述以便我能给出准确的帮助。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值