原文:仿 ElmentUI 实现一个 Form 表单javascript
ElementUI 中 Form 组件主要有如下 功能 / 模块:
vue
在这套组件中,有 3 层嵌套,这里面使用的是 slot 插槽,咱们在接下来的代码中也须要运用到它。
java
<template> <div> <!-- 1.绑定 value 2.响应 input 事件 --> <input type="text" :value="valueInInput" @input="handleInput"> </div> </template> <script> export default { name: 'EInput', props: { value: { type: String, default: '' } }, data() { return { valueInInput: this.value // 确保数据流的单向传递 } }, methods: { handleInput(e) { this.valueInInput = e.target.value; this.$emit('input', this.valueInInput); // 此处提交的事件名必须是 ‘input’ // 数据变了,定向通知 formItem 进行校验 this.dispatch('EFormItem', 'validate', this.valueInInput); }, dispatch(componentName, eventName, params) { // 查找指定 name 的组件, let parent = this.$parent || this.$root; let name = parent.$options.name while(parent && (!name || name !== componentName)) { parent = parent.$parent; if (parent) { name = parent.$options.name; } } if (parent) { // 这里,咱们不能用 this.$emit 直接派发事件,由于在 FormItem 组件中,Input 组件的位置只是一个插槽,没法作事件监听, // 因此此时咱们让 FormItem 本身派发事件,并本身监听。修改 FormItem 组件,在 created 中监听该事件。 parent.$emit.apply(parent, [eventName].concat(params)); } } } } </script>
这里须要注意的是 v-model 绑定的值与 props 传递的值的关系,从而能将修改后的值暴露至顶层自定义组件。使用以下:数组
<template> <div id="app"> <e-input v-model="initValue"></e-input> <div>{{ initValue }}</div> </div> </template> <script> import EInput from './components/Input.vue'; export default { name: "app", components: { EInput }, data() { return { initValue: '223', }; }, }; </script>
<template> <div> <label v-if="label">{{ label }}</label> <div> <!-- 拓展槽 --> <slot></slot> <!-- 验证提示信息 --> <p v-if="validateState === 'error'" class="error">{{ validateMessage }}</p> </div> </div> </template> <script> import AsyncValidator from 'async-validator'; export default { name: 'EFormItem', props: { label: { type: String, default: '' }, // 表单项的名称 prop: { type: String, default: '' } // 表单项的自定义 prop }, inject: ['eForm'], // provide/inject,vue 跨层级通讯 data() { return { validateState: '', validateMessage: '' } }, methods: { validate() { return new Promise(resolve => { const descriptor = {}; // async-validator 建议用法; descriptor[this.prop] = this.eForm.rules[this.prop]; // 校验器 const validator = new AsyncValidator(descriptor); const model = {}; model[this.prop] = this.eForm.model[this.prop]; // 异步校验 validator.validate(model, errors => { if (errors) { this.validateState = 'error'; this.validateMessage = errors[0].message; resolve(false); } else { this.validateState = ''; this.validateMessage = ''; resolve(true); } }) }) }, dispatch(componentName, eventName, params) { // 查找上级指定 name 的组件 var parent = this.$parent || this.$root; var name = parent.$options.name; while (parent && (!name || name !== componentName)) { parent = parent.$parent; if (parent) { name = parent.$options.name; } } if (parent) { parent.$emit.apply(parent, [eventName].concat(params)); } } }, created() { this.$on('validate', this.validate); // 'validate' 事件由 e-input 组件通知,在 e-form-item 组件接收到后自行触发对应方法 }, // 由于咱们须要在 Form 组件中校验全部的 FormItem , // 因此当 FormItem 挂载完成后,须要派发一个事件告诉 Form :你能够校验我了。 mounted() { // 当 FormItem 中有 prop 属性的时候才校验, // 没有的时候不校验。好比提交按钮就不须要校验。 if (this.prop) { this.dispatch('EForm', 'addFiled', this); } } } </script> <style scoped> .error { color: red; } </style>
其中, methods 中的方法均是辅助方法,validate() 是异步校验的方法。缓存
<template> <form> <slot></slot> </form> </template> <script> export default { name: 'EForm', props: { model: { type: Object, required: true }, rules: { type: Object } }, provide() { // provide/inject,vue 跨层级通讯 return { eForm: this // form 组件实例, 在其余组件中能够经过 this.xxx 来获取属性/方法 } }, data() { return { fileds: [] // 须要校验的 e-form-item 组件数组 } }, methods: { async validate(cb) { const eachFiledResultArray = this.fileds.map(filed => filed.validate()); const results = await Promise.all(eachFiledResultArray); let ret = true; results.forEach(valid => { if (!valid) { ret = false; } }); cb(ret) } }, created() { // 缓存须要检验的组件 this.fileds = []; this.$on('addFiled', filed => this.fileds.push(filed)) } } </script>