重庆小程序制作开发_仿ElementUI完成一个Form表单的

发布时间:2021-01-07 14:27 作者:jianzhan

摘要: 仿ElementUI完成一个Form表格的完成编码 本文关键详细介绍了仿ElementUI完成一个Form表格的完成编码,原文中根据实例编码详细介绍的十分详尽,对大伙儿的学习培训或是工作中具备一

仿ElementUI实现一个Form表单的实现代码       这篇文章主要介绍了仿ElementUI实现一个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",
 pro凡科抠图: {
 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 中,我们可以看到:

需要 label 来显示名称; 需要 prop 来校验当前项; 需要给 input 或 button 预留插槽;

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

 template 
 div 
 !-- 解释一 -- 
 label v-if="label" {{ label }} /label 
 div 
 !-- 解释二 -- 
 slot /slot 
 !-- 解释三 -- 
 p v-if="validateState === 'error'" {{ validateMessage }} /p 
 /div 
 /div 
 /template 
 script 
 export default {
 name: "EFormItem",
 pro凡科抠图: {
 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 为我们提供了 provide 和 inject API,方便我们跨层级传递数据。

我们举个例子来简单实现一下 provide 和 inject 。在 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' ],
 pro凡科抠图: {
 value: {
 type: String,
 default: '',
 data() {
 return {
 valueInInput: this.value
 methods: {
 handleInput(event) {
 this.valueInInput = event.target.value;
 this.$emit('input', this.valueInInput);
 /script 

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

理解了上面的知识点后,我们可以开始设计 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',
 pro凡科抠图: { // 解释一
 model: {
 type: Object,
 required: true
 rules: {
 type: Object
 provide() { // 解释二
 return {
 eForm: this // 解释三
 /script 

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

解释二:为了让各个层级都能使用 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 组件中,所以 FormItem 和 Input 组件是有数据传递的。当 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 的组件,
 ponentName, 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 中,验证用到了一个底层库,我们可以通过 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' " {{ validateMessage }} /p 
 /div 
 /div 
 /template 
 script 
import AsyncValidator from "async-validator";
export default {
 name: "EFormItem",
 pro凡科抠图: {
 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);
 // 查找上级指定名称的组件
 ponentName, 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 {
 pro凡科抠图: {
 model: { type: Object, required: true },
 rules: { type: Object }
 provide() {
 return {
 eForm: this, // ponent'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 的 true 或 false 。将所有的结果,放在一个数组 eachFiledResultArray 中。

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

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

 

四. 总结

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

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

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

其次是一些思想:

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

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

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


  • 中国做公司管理方法手机

    我国做企业管理方法方式手机上手机软件的公司仍然分为几大类:一类是针对大中小型企业适合100人及以上的公司,例如金蝶软件手机软件、用友、IBM等制作企业管理方法方式手机上手

  • 华为公司云+阿帕互联网构

    模拟题目:华为公司企业云+阿帕互连网搭建临沂市市聪明伶俐货运物流货运物流园 临沂市市对外经济贸易的发展趋势发展趋势催产了货运物流货运物流的面世,经历三十好多年的发展

  • 跨境电商电子商务:为何

    模拟题目:跨境电商电子商务电子器件商务接待:为什么要做独立站?独立站该怎样去做? 为什么要建独立站? 根据我工作中中十年的工作中工作经验,之前在一家开售企业做了六年,

  • 公司使用云服务器,教你

    模拟题目:企业应用云服务器器,教你get适当姿势 随着着绝大部分据和云服务器器的逐渐完善和迅速发展趋势发展趋势,越来越越越大企业选择将应用和数据信息信息内容迁移到云空间

  • 阿里巴巴云全新升级发布

    模拟题目:阿里巴巴巴巴云全新升级升級公布轻量应用互联网网络服务器,致力于于提升顾客互联网网络服务器运用体会 技术性性解读 阿里巴巴巴巴云轻量应用互联网网络服务器运用依