仿ElementUI实现一个Form表单的实现代码

使用组件就像流水线上的工人;设计组件就像设计流水线的人,设计好了给工人使用。

完整项目地址:仿 ElementtUI 实现一个 Form 表单

一. 目标

仿 ElementUI 实现一个简单的 Form 表单,主要实现以下四点:

  • Form
  • FormItem
  • Input
  • 表单验证

我们先看一下 ElementUI 中 Form 表单的基本用法

<el-form :model="ruleForm" :rules="rules" ref="loginForm">

   <el-form-item label="用户名" prop="name">
    	<el-input v-model="ruleForm.name"></el-input>
   </el-form-item>

   <el-form-item label="密码" prop="pwd">
    	<el-input v-model="ruleForm.pwd"></el-input>
   </el-form-item>

   <el-form-item>
    	<el-button type="primary" @click="submitForm('loginForm')">登录</el-button>
   </el-form-item>

</el-form>

在 ElementUI 的表单中,主要进行了 3 层嵌套关系, Form 是最外面一层, FormItem 是中间一层,最内层是 Input 或者 Button

二. 创建项目

我们通过 Vue CLI 3.x 创建项目。

使用 vue create e-form 创建一个目录。

使用 npm run serve 启动项目。

三. Form 组件设计

ElementUI 中的表单叫做 el-form ,我们设计的表单就叫 e-form

为了实现 e-form 表单,我们参考 ElementUI 的表单用法,总结出以下我们需要设计的功能。

  • e-form 负责全局校验,并提供插槽;
  • e-form-item 负责单一项校验及显示错误信息,并提供插槽;
  • e-input 负责数据双向绑定;

1. Input 的设计

我们首先观察一下 ElementUI 中的 Input 组件:

<el-input v-model="ruleForm.name"></el-input>

在上面的代码中,我们发现 input 标签可以实现一个双向数据绑定,而实现双向数据绑定需要我们在 input 标签上做两件事。

  • 要绑定 value
  • 要响应 input 事件

当我们完成这两件事以后,我们就可以完成一个 v-model 的语法糖了。

我们创建一个 Input.vue 文件:

<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(event) {
     this.valueInInput = event.target.value; // 解释三
    	this.$emit('input', this.valueInInput); // 解释四
   }
 },
};
</script>

我们对上面的代码做一点解释:

**解释一:**既然我们想做一个 Input 组件,那么接收的值必然是父组件传进来的,并且当父组件没有传进来值的时候,我们可以它一个默认值 ""

**解释二:**我们在设计组件的时候,要遵循单向数据流的原则:父组件传进来的值,我们只能用,不能改。那么将父组件传进来的值进行一个赋值操作,赋值给 Input 组件内部的 valueInInput ,如果这个值发生变动,我们就修改内部的值 valueInInput 。这样我们既可以处理数据的变动,又不会直接修改父组件传进来的值。

**解释三:**当 Input 中的值发生变动时,触发 @input 事件,此时我们通过 event.target.value 获取到变化后的值,将它重新赋值给内部的 valueInInput

**解释四:**完成了内部赋值之后,我们需要做的就是将变化后的值通知父组件,这里我们用 this.$emit 向上派发事件。其中第一个参数为事件名,第二个参数为变化的值。

完成了以上四步,一个实现了双向数据绑定的简单的 Input 组件就设计完成了。此时我们可以在 App.vue 中引入 Input 组件观察一下结果。

<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>

2. FormItem 的设计

<el-form-item label="用户名" prop="name">
		<el-input v-model="ruleForm.name"></el-input>
</el-form-item>

在 ElementUI 的 formItem 中,我们可以看到:

  1. 需要 label 来显示名称;
  2. 需要 prop 来校验当前项;
  3. 需要给 inputbutton 预留插槽;

根据上面的需求,我们可以创建出自己的 formItem ,新建一个 FormItem.vue 文件 。

<template>
  <div>
    <!-- 解释一 -->
    <label v-if="label">{{ label }}</label>
    <div>
      <!-- 解释二 -->
      <slot></slot>
      <!-- 解释三 -->
      <p v-if="validateState === 'error'" class="error">{{ validateMessage }}</p>
    </div>
  </div>
</template>

<script>
  export default {
    name: "EFormItem",
    props: {
   			label: { type: String, default: '' },
   			prop: { type: String, default: '' }
 			},
    data() {
      return {
        validateState: '',
        validateMessage: ''
      }
    },
  }
</script>

<style scoped>
.error {
  color: red;
}
</style>

和上面一样,我们接着对上面的代码进行一些解释:

**解释一:**根据 ElementUI 中的用法,我们知道 label 是父组件传来,且当传入时我们展示,不传入时不展示。

解释二: slot 是一个预留的槽位,我们可以在其中放入 input 或其他组件、元素。

解释三: p 标签是用来展示错误信息的,如果验证状态为 error 时,就显示。

此时,我们的 FormItem 组件也可以使用了。同样,我们在 App.vue 中引入该组件。

<template>
 <div id="app">

  <e-form-item label="用户名" prop="name">
   	<e-input v-model="ruleForm.name"></e-input>
  </e-form-item>

  <e-form-item label="密码" prop="pwd">
   	<e-input v-model="ruleForm.pwd"></e-input>
  </e-form-item>

  <div>
   {{ ruleForm }}
  </div>

 </div>
</template>

<script>
import EInput from './components/Input.vue';
import EFormItem from './components/FormItem.vue';

export default {
 name: "app",
 components: {
  EInput,
  EFormItem
 },
 data() {
  return {
   ruleForm: {
    name: '',
    pwd: '',
   },
  };
 },
};
</script>

3. Form 的设计

到现在,我们已经完成了最内部的 input 以及中间层的 FormItem 的设计,现在我们开始设计最外层的 Form 组件。

当层级过多并且组件间需要进行数据传递时,Vue 为我们提供了 provideinject API,方便我们跨层级传递数据。

我们举个例子来简单实现一下 provideinject 。在 App.vue 中,我们提供数据(provide)。

export default {
 name: "app",
 provide() {
  return {
   msg: '哥是最外层提供的数据'
  }
 }
};
</script>

接着,我们在最内层的 Input.vue 中注入数据,观察结果。

<template>
 <div>
  <!-- 1、绑定 value
  2、响应 input 事件-->
  <input type="text" :value="valueInInput" @input="handleInput">
  <div>{{ msg }}</div>
 </div>
</template>

<script>
export default {
 name: "EInput",
 inject: [ 'msg' ],
 props: {
  value: {
   type: String,
   default: '',
  }
 },
 data() {
  return {
   valueInInput: this.value
  };
 },
 methods: {
   handleInput(event) {
     this.valueInInput = event.target.value;
    	this.$emit('input', this.valueInInput);
   }
 },
};
</script>

根据上图,我们可以看到无论跨越多少层级, provideinject 可以非常方便的实现数据的传递。

理解了上面的知识点后,我们可以开始设计 Form 组件了。

<el-form :model="ruleForm" :rules="rules" ref="loginForm">

</el-form>

根据 ElementUI 中表单的用法,我们知道 Form 组件需要实现以下功能:

  • 提供数据模型 model;
  • 提供校验规则 rules;
  • 提供槽位,里面放我们的 FormItem 等组件;

根据上面的需求,我们创建一个 Form.vue 组件:

<template>
  <form>
    <slot></slot>
  </form>
</template>

<script>
  export default {
    name: 'EForm',
    props: { // 解释一
      model: {
        type: Object,
        required: true
      },
      rules: {
        type: Object
      }
    },
    provide() { // 解释二
      return {
        eForm: this // 解释三
      }
    }
  }
</script>

解释一:该组件需要用户传递进来一个数据模型 model 进来,类型为 Objectrules 为可传项。

解释二:为了让各个层级都能使用 Form 中的数据,需要依靠 provide 函数提供数据。

解释三:直接将组件的实例传递下去。

完成了 Form 组件的设计,我们在 App.vue 中使用一下:

<template>
 <div id="app">

  <e-form :model="ruleForm" :rules="rules">

   <e-form-item label="用户名" prop="name">
    <e-input v-model="ruleForm.name"></e-input>
   </e-form-item>

   <e-form-item label="密码" prop="pwd">
    <e-input v-model="ruleForm.pwd"></e-input>
   </e-form-item>

   <e-form-item>
    <button>提交</button>
   </e-form-item>

 	</e-form>
 </div>
</template>

<script>
import EInput from './components/Input.vue';
import EFormItem from './components/FormItem.vue';
import EForm from "./components/Form";

export default {
 name: "app",
 components: {
  EInput,
  EFormItem,
  EForm
 },
 data() {
  return {
   ruleForm: {
    name: '',
    pwd: '',
   },
   rules: {
    name: [{ required: true }],
    pwd: [{ required: true }]
   },
  };
 },
};
</script>

到目前为止,我们的基本功能就已经实现了,除了提交与验证规则外,所有的组件几乎与 ElementUI 中的表单一模一样了。下面我们就开始实现校验功能。

4. 设计校验规则

在上面设计的组件中,我们知道校验当前项和展示错误信息的工作是在 FormItem 组件中,但是数据的变化是在 Input 组件中,所以 FormItemInput 组件是有数据传递的。当 Input 中的数据变化时,要告诉 FormItem ,让 FormItem 进行校验,并展示错误。

首先,我们修改一下 Input 组件:

methods: {
  handlerInput(event) {
   this.valueInInput = event.target.value;
   this.$emit("input", this.valueInInput);

   // 数据变了,定向通知 FormItem 校验
   this.dispatch('EFormItem', 'validate', this.valueInput);
  },
		// 查找指定 name 的组件,
  dispatch(componentName, eventName, params) {
   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));
   }
  }
 }

这里,我们不能用 this.$emit 直接派发事件,因为在 FormItem 组件中, Input 组件的位置只是一个插槽,无法做事件监听,所以此时我们让 FormItem 自己派发事件,并自己监听。修改 FormItem 组件,在 created 中监听该事件。

created() {
	this.$on('validate', this.validate);
}

Input 组件中的数据变化时, FormItem 组件监听到 validate 事件后,执行 validate 函数。

下面,我们就要处理我们的 validate 函数了。而在 ElementUI 中,验证用到了一个底层库async-validator,我们可以通过 npm 安装这个包。

npm i async-validator

async-validator 是一个可以对数据进行异步校验的库,具体的用法可以参考上面的链接。我们通过这个库来完成我们的 validate 函数。继续看 FormItem.vue 这个文件:

<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: '' }
 },
 inject: ["eForm"], // 解释一
 created() {
  this.$on("validate", this.validate);
 },
 mounted() { // 解释二
  if (this.prop) { // 解释三
   this.dispatch('EForm', 'addFiled', this);
  }
 },
 data() {
  return {
   validateMessage: "",
   validateState: ""
  };
 },
 methods: {
  validate() {
    // 解释四
   return new Promise(resolve => {
    // 解释五
    const descriptor = {
     // name: this.form.rules.name =>
     // name: [ { require: true }, { ... } ]
    };
    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) {
   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));
   }
  }
 }
};
</script>

<style scoped>
.error {
 color: red;
}
</style>

我们对上面的代码做一个解释。

解释一:注入 Form 组件提供的数据 - Form 组件的实例,下面就可以使用 this.eForm.xxx 来使用 Form 中的数据了。

解释二:因为我们需要在 Form 组件中校验所有的 FormItem ,所以当 FormItem 挂载完成后,需要派发一个事件告诉 Form :你可以校验我了。

解释三:当 FormItem 中有 prop 属性的时候才校验,没有的时候不校验。比如提交按钮就不需要校验。

<e-form-item>
		<input type="submit" @click="submitForm()" value="提交">
</e-form-item>

**解释四:**返回一个 promise 对象,批量处理所有异步校验的结果。

解释五: descriptor 对象是 async-validator 的用法,采用键值对的形式,用来检查当前项。比如:

// 检查当前项
// async-validator 给出的例子
name: {
		type: "string",
		required: true,
		validator: (rule, value) => value === 'muji',
}

FormItem 中检查当前项完成了,现在我们需要处理一下 Form 组件中的全局校验。表单提交时,需要对 form 进行一个全局校验。大致的思路是:循环遍历表单中的所有派发上来的 FormItem ,让每一个 FormItem 执行自己的校验函数,如果有一个为 false ,则校验不通过;否则,校验通过。我们通过代码实现一下:

<template>
 <form>
  <slot></slot>
 </form>
</template>

<script>
  export default {
    props: {
      model: { type: Object, required: true },
      rules: { type: Object }
    },
    provide() {
     return {
       eForm: this, // provide this component's instance
     }
    },
	   data() {
      return {
        fileds: [],
      }
    },
    created() {
      // 解释一
     	this.fileds = [];
      this.$on('addFiled', filed => this.fileds.push(filed));
    },
    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);
      }
    },
  }
</script>

<style lang="scss" scoped>
</style>

解释一:用 fileds 缓存需要校验的表单项,因为我们在 FormItem 中派发了事件。只有需要校验的 FormItem 会被派发到这里,而且都会保存在数组中。

if (this.prop) {
   this.dispatch('EForm', 'addFiled', this);
}

解释二:当点击提交按钮时,会触发这个事件。

解释三:遍历所有被添加到 fileds 中的 FormItem 项,让每一项单独去验证,会返回 Promise 的 truefalse 。将所有的结果,放在一个数组 eachFiledResultArray 中。

解释四:获取所有的结果,统一进行处理,其中有一个结果为 false ,验证就不能通过。

至此,一个最简化版本的仿 ElementUI 的表单就实现了。

四. 总结

当然上面的代码还有很多可以优化的地方,比如说 dispatch 函数,我们可以写一遍,使用的时候用 mixin 导入。由于篇幅关系,这里就不做处理了。

通过这次实现,我们首先总结一下其中所涉及的知识点。

  • 父组件传递给子组件用 props
  • 子组件派发事件,用 $emit
  • 跨层级数据交互,用 provide 和 inject
  • 用 slot 可以预留插槽

其次是一些思想:

  • 单项数据流:父组件传递给子组件的值,子组件内部只能用,不能修改。
  • 组件内部的 name 属性,可以通过 this.$parent.$options.name 查找。
  • 想要批量处理很多异步的结果,可以用 promise 对象。

最后,文章会首先发布在我的 Github,以及公众号上,欢迎关注,欢迎 star。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Vue ElementUI之Form表单验证遇到的问题

    首先说一下 我在form表单里面遇见的坑: 1.例如我要给后台传的不是对象,而是一个数组,怎么写验证? 2.比如我有四个弹出框,都要做验证,这个时候就要注意了,每一个弹出框的ref都不能给的一样,并且一定要与当前弹框的确定或者保存按钮一一对应,例如:第一个弹框的ref='number',按钮的click比如为xxxxxx('number'),第二个弹出框的ref='number2',对应的按钮>>xxxxxx('number2').如果ref用的都一样,就会出现,点击下一步我没有去做验证,我再

  • vue+element-ui集成随机验证码+用户名+密码的form表单验证功能

    在登入页面,我们往往需要通过输入验证码才能进行登入,那我们下面就详讲一下在vue项目中如何配合element-ui实现这个功能 第一步:自定义一个生产随机验证码的组件,其本质是使用canvas绘制,详细代码如下: <template> <div class="s-canvas"> <canvas id="s-canvas" :width="contentWidth" :height="contentHeig

  • js Form.elements[i]的使用实例

    复制代码 代码如下: //检查表单元素是否为空 function check(Form) { for (i=0;i<Form.length;i++){ if(Form.elements[i].value == "") { //Form的属性elements的首字e要小写 alert(Form.elements[i].name + "不能为空!"); Form.elements[i].focus(); //指定表单元素获得焦点 return; } } Form.

  • vue elementui form表单验证的实现

    最近我们公司将前端框架由easyui 改为 vue+elementui .自学vue两周 就开始了爬坑之路.业余时间给大家分享一下心得,技术新手加上第一次分享(小激动),有什么不足的地方欢迎大家指正,多多交流才能共同进步! 1.问题 我们公司的项目比较大 表格 表单的页面都不胜数 ,基于此封装了一些 可复用的代码. 2.分析  vue给了我们不一样的前端代码体验  element ui 给我们一套功能强大的组件 减少了我们大量的开发时间 .双剑合璧 天下无敌!  但每个公司的代码风格不同  用户

  • JS.getTextContent(element,preformatted)使用介绍

    复制代码 代码如下: /*获取标签的文字*/ function getTextContent(element, preformatted) { if (!elementIsVisible(element)) return ''; if (element.nodeType == 3 /*Node.TEXT_NODE*/) { var text = element.data; if (!preformatted) { //text = text.replace(/\n|\r|\t/g, " &quo

  • 详解element-ui中form验证杂记

    最近接触的商户后台项目居多,自然而然就涉及到了大量的表单验证, 也就对一些常用的el-form表单验证和问题进行下梳理. 当我们添加required验证后,输入一些数据后再删除完时,会出现xxx is required,如下图所示 你可能很纳闷,已经为form表单传入了rules了啊,在不全部删除时,验证规则都没问题.这个问题是因为在html中使用了required字段,而在rules的规则没有设置required为true, // html <el-form-item label="角色

  • 仿ElementUI实现一个Form表单的实现代码

    使用组件就像流水线上的工人:设计组件就像设计流水线的人,设计好了给工人使用. 完整项目地址:仿 ElementtUI 实现一个 Form 表单 一. 目标 仿 ElementUI 实现一个简单的 Form 表单,主要实现以下四点: Form FormItem Input 表单验证 我们先看一下 ElementUI 中 Form 表单的基本用法 <el-form :model="ruleForm" :rules="rules" ref="loginFo

  • vue+elementui(对话框中form表单的reset问题)

    目录 对话框中form表单的reset问题 解决原理 解决办法 element UI form表单重置无效 实例化是 说下解决 对话框中form表单的reset问题 一般在新增和编辑的时候用的都是同一个对话框和form表单,而在先点击编辑的时候form表单的resetfileds函数就会失效 解决原理 实际上结构是(通过vue类比) data里面有一个form表单的初始值, methods里面定义了一个resetfileds的函数 resetfileds函数的作用:记录在mounted生命周期执

  • 使用Vue动态生成form表单的实例代码

    具有数据收集.校验和提交功能的表单生成器,包含复选框.单选框.输入框.下拉选择框等元素以及,省市区三级联动,时间选择,日期选择,颜色选择,文件/图片上传功能,支持事件扩展. 欢迎大家star学习交流:github地址 示例 https://raw.githubusercontent.com/xaboy/form-create/dev/images/sample110.jpg 安装 npm install form-create OR git clone https://github.com/xa

  • Bootbox将后台JSON数据填充Form表单的实例代码

    序言: 刚结束公司的三个月试用期,意味着我即将正式步入社会成为广大从事IT行业的一员.作为一个编程小白,无论从技术层面还是知识层面都是比较薄弱的,想要成为一个优秀的程序员不断的学习与探索是不可避免的.我相信一切的付出与收获是成正比的!Fighting! 这几天在做公司的实际项目的时候,需要实现选中Bootstrap table中的任意一行数据点击编辑按钮弹出一个模态框以表单的形式对该行数据进行编辑.获取表格行的数据是比较方便的,具体可以查找Bootstrap table参考文档,具体地址可以直接

  • Jquery让form表单异步提交代码实现

    这篇文章主要介绍了Jquery让form表单异步提交代码实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 1.监听表单提交事件,并阻止表单提交 $("form").submit(function(e) { return false;//阻止表单提交 }) 2.拿到表单内容 let data = $("form").serialize(); //上边这个就是拿到表单的内容,如果你想要json格式,就要自己去处理 /

  • 仅一个form表单 js实现注册信息依次填写提交功能

    我们原先是一个很长的form表单,里面有很多选项.客户反馈这样不够友好,容易看花眼.因此进行改进,实现多步骤进度,多个提交的实现(其实只有一个form提交). 实现的思路:将表单的选项装入多个div中,一个显示,其他隐藏. 实现效果如下: 1.JavaScript代码 <script type="text/javascript" src="js/jquery.js"></script> <script type="text/j

  • php中一个完整表单处理实现代码

    一个完整表单处理 下面我们将创建一个复杂的表单,代码如下所示. 复制代码 代码如下: <form action="someform.php" method="post"> <table width="541" border="0"> <tr> <td width="26%">姓名:</td> <td width="74%"

  • 解析在zend Farmework下如何创立一个FORM表单

    1.首先让我们设置一下我们的程序,让Zend能够自动载入方法,不需要我们手动的去载入 复制代码 代码如下: require_once 'Zend/Loader/Autoloader.php'    //载入自动加载类$loader = Zend_Loader_Autoloader::getInstance();//自动实例化$loader->registerNamespace('Application_');//注册命名空间(只有系统默认的,和注册的才可以被自动载入)$loader->regi

  • Ajax提交Form表单及文件上传的实例代码

    前几天,发现了一些小问题.我在写后台管理页面时,需要上传一张图片.于是我就用很普通的Form表单上传有一段Json串和图片文件: Form表单上传图片只需要在<form>标签里加上enctype = 'multipart/form-data',这样是可以上传图片的: 但问题来了,在我进行用Form表单提交的时候直接跳出来提交返回值的页面并且原先的页面刷新: 这样我们可以先到异步的Ajax可以实现局部刷新: 废话不多说了 直接上代码: 首先是html: <form id = "f

  • jQuery EasyUI API 中文文档 - Form表单

    Form 表单 用法 复制代码 代码如下: <form id="ff" method="post"> ... </form> 使 form 成为 ajax 提交的 form . 复制代码 代码如下: $('#ff').form({ url:..., onSubmit: function(){ // 做某些检查 // 返回 false 来阻止提交 }, success:function(data){ alert(data) } }); // 提

随机推荐