原文:仿 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>