记:组件库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 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰5 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
前端小万5 天前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝5 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
三十而立洋5 天前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
卡布鲁5 天前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
汉堡大王95275 天前
Jev:不是聊天机器人, 而是一个智能 if 语句
前端·人工智能·后端
梦想很大很大5 天前
从运行事实到回归证据:Workrun 的 Telemetry 与 Evaluation 实践
前端·人工智能·后端
计算机魔术师5 天前
Meta Muse agent 接入 Shopify 的 Shop Pay 实现代理式购物
前端
沙蒿同学5 天前
我用 Go 搭了一条 AI Agent 流水线:从 1 张商品图到一整套淘宝详情页
前端·javascript·后端