记:组件库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);
  }
});
相关推荐
coding随想30 分钟前
JavaScript ES6 解构:优雅提取数据的艺术
前端·javascript·es6
小小小小宇35 分钟前
一个小小的柯里化函数
前端
灵感__idea39 分钟前
JavaScript高级程序设计(第5版):无处不在的集合
前端·javascript·程序员
小小小小宇42 分钟前
前端双Token机制无感刷新
前端
小小小小宇1 小时前
重提React闭包陷阱
前端
小小小小宇1 小时前
前端XSS和CSRF以及CSP
前端
UFIT1 小时前
NoSQL之redis哨兵
java·前端·算法
超级土豆粉1 小时前
CSS3 的特性
前端·css·css3
星辰引路-Lefan1 小时前
深入理解React Hooks的原理与实践
前端·javascript·react.js
wyn200011281 小时前
JavaWeb的一些基础技术
前端