记:组件库Form、FormItem的实现原理

要点

  1. Form、FormItem的数据通信
  2. Form的表单校验
  3. 表单提交

设计思路

Form用于管理form表单数据,配置校验规则。 FormItem用于添加具体表单项的数据绑定。

vue 复制代码
<!-- Form 使用 -->
<el-form ref="formRef" :model="formData" :rules="formRules">
  <el-form-item prop="username">
    <el-input v-model="formData.username" />
  </el-form-item>
</el-form>

Form、FormItem的数据通信

插槽

通过插槽(slot)机制,支持自定义表单项的内容

数据绑定

v-model实现form的双向绑定;

数据通信

provide、inject实现Form向FormItem传递form实例、校验规则

Form组件内存储所有FormItem实例,回传方法通过provide传入

js 复制代码
// Form 组件
export default defineComponent({
  name: 'ElForm',
  setup(props, { expose }) {
    const fields = ref<FormItemContext[]>([]); // 存储所有 FormItem 实例
    // 向子组件提供 Form 实例
    provide('formRef', {
      registerField: (field: FormItemContext) => {
        fields.value.push(field);
      },
      unregisterField: (field: FormItemContext) => {
        fields.value = fields.value.filter(f => f !== field);
      },
      model: props.model,        // 表单数据对象
      rules: props.rules,        // 校验规则
    });
  }
});

FormItem校验规则

Element Plus 基于 async-validator 库实现校验逻辑

js 复制代码
// FormItem 组件
export default defineComponent({
  name: 'ElFormItem',
  props: {
    prop: String,        // 绑定到 model 的字段名
    rules: [Object, Array] // 字段专属校验规则(覆盖全局)
  },
  setup(props) {
    const form = inject('formRef'); // 获取 Form 实例
    const errorMessage = ref('');

    // 校验逻辑
    const validateField = () => {
      const rules = props.rules || form.rules?.[props.prop];
      const validator = new AsyncValidator({
        [props.prop]: rules
      });
      return validator.validate({ [props.prop]: form.model[props.prop] })
        .then(() => {
          errorMessage.value = '';
        })
        .catch(({ errors }) => {
          errorMessage.value = errors[0].message;
          return Promise.reject(errors);
        });
    };

    // 注册到 Form 的校验队列
    onMounted(() => {
      form?.registerField({
        validate: () => validateField(),
      });
    });

    return { errorMessage };
  }
});

全局校验

由于上述 FormItem校验规则 中已将校验方法回存至 Form 中,因此全局校验只需遍历执行各个 FormItem 中的校验方法即可

js 复制代码
// Form 组件
export default defineComponent({
  name: 'ElForm',
  setup(props, { expose }) {
    const fields = ref<FormItemContext[]>([]); // 存储所有 FormItem 实例
    // 向子组件提供 Form 实例
    provide('formRef', {
      registerField: (field: FormItemContext) => {
        fields.value.push(field);
      },
      unregisterField: (field: FormItemContext) => {
        fields.value = fields.value.filter(f => f !== field);
      },
      model: props.model,        // 表单数据对象
      rules: props.rules,        // 校验规则
    });
    
    // 全局校验
    const validateForm = () => {
        fields.value.forEach(formItem  => {
            formItem?.validate && formItem?.validate();
        })
    };
    
    // 暴露校验方法
    defineExpose({
        validate: validateForm,
    });
  }
});

表单提交

对form的submit事件监听器

js 复制代码
const fields = ref<FormItemContext[]>([]); // 存储所有 FormItem 实例
// 向子组件提供 Form 实例
provide('formRef', {
    registerField: (field: FormItemContext) => {
        fields.value.push(field);
    },
    unregisterField: (field: FormItemContext) => {
        fields.value = fields.value.filter(f => f !== field);
    },
    model: props.model,        // 表单数据对象
    rules: props.rules,        // 校验规则
});

// 全局校验
const validateForm = (cb) => {
    let promisAll = [];
    promisAll = fields.value.filter(formItem  => {
        return typeof formItem?.validate === 'function';
    }).map(formItem => formItem.validate());
    Promise.all(promisAll).then((res) => {
        let rej = res.filter(item => item);
        cb && cb(!rej.length);
    })
};

// 暴露校验方法
defineExpose({
    validate: validateForm,
});
const formEl = ref(null); // 表单元素的引用

const handleSubmit = (event) => {
  event.preventDefault(); // 阻止默认提交行为
  validateForm.value((valid) => {
    if (valid) {
      emit('submit', formModel); // 触发用户定义的 submit 事件
    } else {
      console.log('Form validation failed');
    }
  });
};

onMounted(() => {
  if (formEl.value) {
    formEl.value.addEventListener('submit', handleSubmit);
  }
});

onBeforeUnmount(() => {
  if (formEl.value) {
    formEl.value.removeEventListener('submit', handleSubmit);
  }
});
相关推荐
程序员爱钓鱼2 小时前
Node.js 编程实战:文件读写操作
前端·后端·node.js
PineappleCoder2 小时前
工程化必备!SVG 雪碧图的最佳实践:ID 引用 + 缓存友好,无需手动算坐标
前端·性能优化
JIngJaneIL2 小时前
基于springboot + vue古城景区管理系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot·后端
敲敲了个代码2 小时前
隐式类型转换:哈基米 == 猫 ? true :false
开发语言·前端·javascript·学习·面试·web
澄江静如练_3 小时前
列表渲染(v-for)
前端·javascript·vue.js
JustHappy3 小时前
「chrome extensions🛠️」我写了一个超级简单的浏览器插件Vue开发模板
前端·javascript·github
Loo国昌3 小时前
Vue 3 前端工程化:架构、核心原理与生产实践
前端·vue.js·架构
sg_knight3 小时前
拥抱未来:ECMAScript Modules (ESM) 深度解析
开发语言·前端·javascript·vue·ecmascript·web·esm
LYFlied3 小时前
【每日算法】LeetCode 17. 电话号码的字母组合
前端·算法·leetcode·面试·职场和发展