记:组件库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);
  }
});
相关推荐
冰凉小脚5 分钟前
vue3 数据监听(watch、watchEffect)
前端·javascript·vue.js
GUIQU.29 分钟前
【HTML】验证与调试工具
前端·html
前端菜鸟来报道31 分钟前
react + css 实现 椭圆布局
前端·css·椭圆布局
bin915335 分钟前
DeepSeek 助力 Vue3 开发:打造丝滑的表格(Table)之添加行拖拽排序功能示例4,TableView16_04 跨表格拖拽示例
前端·javascript·vue.js·ecmascript·deepseek
玄魂37 分钟前
报表优化实战:组件库Table升级VTable
前端·开源·数据可视化
琹箐42 分钟前
js文字两端对齐
前端·javascript·css
摆烂工程师44 分钟前
炸裂了~兄弟们,GPT4o出图效果太好了
前端·后端·程序员
开心小老虎1 小时前
用HTML和CSS生成炫光动画卡片
前端·css·html
米粒宝的爸爸1 小时前
vue3 vue-router 传递路由参数
前端·javascript·vue.js
前端同学1 小时前
react版本主要区别
前端·react.js·前端框架