记:组件库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);
  }
});
相关推荐
博客zhu虎康8 分钟前
React Hooks 报错?一招解决useState问题
前端·javascript·react.js
灰海21 分钟前
vue中通过heatmap.js实现热力图(多个热力点)热区展示(带鼠标移入弹窗)
前端·javascript·vue.js·heatmap·heatmapjs
王源骏1 小时前
LayaAir鼠标(手指)控制相机旋转,限制角度
前端
大虾写代码1 小时前
vue3+TS项目配置Eslint+prettier+husky语法校验
前端·vue·eslint
wordbaby1 小时前
用 useEffectEvent 做精准埋点:React analytics pageview 场景的最佳实践与原理剖析
前端·react.js
上单带刀不带妹1 小时前
在 ES6 中如何提取深度嵌套的对象中的指定属性
前端·ecmascript·es6
excel2 小时前
使用热力贴图和高斯函数生成山峰与等高线的 WebGL Shader 解析
前端
wyzqhhhh2 小时前
组件库打包工具选型(npm/pnpm/yarn)的区别和技术考量
前端·npm·node.js
码上暴富2 小时前
vue2迁移到vite[保姆级教程]
前端·javascript·vue.js
土了个豆子的2 小时前
04.事件中心模块
开发语言·前端·visualstudio·单例模式·c#