IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> vue表单组件实现(vue2.x) -> 正文阅读

[JavaScript知识库]vue表单组件实现(vue2.x)


本文从零开始实现一个自定义的vue2.x表单组件my-form,组件使用体验类似element-ui

实现过程涉及到的知识点

  1. 自定义事件
  2. 事件的广播与派发
  3. v-mode语法糖原理
  4. $attrs,参考https://cn.vuejs.org/v2/api/#vm-attrs
  5. provide/inject传递数据,参考https://cn.vuejs.org/v2/api/#provide-inject

需求拆解

  • 实现组件my-form,处理表单整体校验、表单data维护,表单rules校验规则维护
  • 实现组件my-form-item,处理单个表单项组件的校验,显示表单label, 校验错误信息
  • 实现组件my-input用于测试表单组件

my-form框架

新建my-form.vue,实现拆解需求提供的功能

  1. 接受model,保存表单数据
  2. 接受校验规则
  3. 提供表单整体校验方法validate,调用子组件my-form-item的校验方法

先上一段伪代码,展示组件基本结构

<template>
  <div>
    <slot></slot>
  </div>
</template>

<script>
export default {
  props: {
    model: {
      type: Object,
      required: true,
    },
    rules: Object,
  },
  methods: {
    validate () {},
  },
};
</script>

my-form-item框架

async-validator三方库实现校验,antd和ElementUi也是使用的这个库,用法参考https://www.npmjs.com/package/async-validator

  1. 接受label,用于显示表单项文本
  2. 接受prop,当前表单项的key,用于获取校验规则、表单项的值。
  3. 提供validate方式,校验当前表单项
  4. 注册自定事件validate,表单项的具体控件如my-inputblur或者change时调用该方法进行校验
<template>
  <div>
    div
    <label v-if="label">{{ label }}</label>
    <!-- 显示表单元素 -->
    <slot></slot>
    <!-- 显示错误信息 -->
    <p v-if="error"
       class="error">{{ error }}</p>
  </div>
</template>

<script>
import Schema from "async-validator";
export default {
  props: {
    label: {
      type: String,
      default: "",
    },
    prop: {
      type: String,
      default: "",
    },
  },
  data () {
    return {
      error: "",
    };
  },
  mounted () {
    // 注册自定义事件validate,表单项的具体控件如my-input在blur或者change时调用该方法进行校验
    this.$on("validate", () => {
      this.validate();
    });
  },
  methods: {
    // my-form 调用
    validate () {},
  },
};
</script>

<style lang="less">
.error {
  color: red;
}
</style>

my-form-item组件校验

校验疑问:校验的过程其实就是规则和表单项的值进行匹配,但是my-form-item组件又没有保存表单项的值,该怎么办呢?回想下在使用ElementUI的时候,我们并没有显示传递表单项的值,她是怎样做到呢,其实是通过provide/inject实现的。

my-form中将实例provide给子孙后代

// my-form.vue文件
provide () {
    return {
      form: this,
    };
  },

在子孙后代组件my-form-item中通过inject接受

// my-form-item.vue文件
import Schema from "async-validator"; // 用法参考https://www.npmjs.com/package/async-validator
export default {
  inject: ["form"],
  // xxx省略其他代码
  methods: {
    validate () {
      // 当前表单项校验
      // 获取校验规则和当前数据
      if(!this.prop) return
      const rules = this.form.rules[this.prop];
      // 删除不用的属性,否则async-validator报错
      rules.forEach(item=>Reflect.deleteProperty(item, 'trigger'))
      const value = this.form.model[this.prop];
      const validator = new Schema({ [this.prop]: rules });
      // 返回一个promise
      return validator.validate({ [this.prop]: value }, (errors) => {
        // errors存在则校验失败
        if (errors) {
          this.error = errors[0].message;
        } else {
          // 校验通过
          this.error = "";
        }
      });
    },
  },
};

my-form组件校验

my-form-item组件已基本实现校验,继续把目光放到my-form组件,它的校验思路是:

  • 接受一个回调函数

  • 收集所有的子组件my-form-itemvalidate并全部触发

  • 将校验结果作为参数,传递给回调函数执行

  methods: {
    validate (cb) {
      // 收集所有的子组件my-form-item的validate
      const tasks = this.$children
        .filter((item) => item.prop)
        .map((item) => item.validate());

      Promise.all(tasks)
        .then(() => cb(true))
        .catch(() => {
          console.log("catch-false");
          cb(false);
        });
    },
  },

在收集my-form-itemvalidate方法时,我们使用this.$children获取子组件,这里会有一个很大的问题,子组件my-form-itemmy-form有可能不是直接父子关系,他们之间可能有其他组件或元素,所以我们需要一个方法去递归遍历my-form的所有子元素,找出所有的my-form-item触发validate。其代码实现过程拆分成如下几个部分:

  • 定义组件标识,要找出item组件,首先我们要给所有item组件加一个标识(组件名称),此处继续参考elementUI,每个组件都有一个componentName属性

    // my-form-item.vue文件
    componentName: "my-form-item",
    
  • 定义广播方法broadcast,用于递归遍历子元素,找出目标组件,触发目标事件

broadcast事件广播

定义broadcast方法递归遍历子元素,找出目标组件,触发目标事件,然后将其写入一个mixins里,方便每一个组件使用,

新建一个emitter.js文件:

// emitter.js文件
function broadcast(componentName, eventName, params) {
  this.$children.forEach((child) => {
    var name = child.$options.componentName;

    if (name === componentName) {
      child.$emit.apply(child, [eventName].concat(params));
    } else {
      broadcast.apply(child, [componentName, eventName].concat([params]));
    }
  });
}
export default {
  methods: {
    broadcast(componentName, eventName, params) {
      broadcast.call(this, componentName, eventName, params);
    },
  },
};

broadcast替换my-form.vuethis.$children的写法,解决父子组件耦合关系

    // my-form.vue文件
  mixins:[emitter],
  methods: {
    validate (cb) {
      const tasks = this.broadcast('my-form-item','validate','')
      // const tasks = this.$children
      //   .filter((item) => item.prop)
      //   .map((item) => item.validate());

      Promise.all(tasks)
        .then(() => cb(true))
        .catch(() => {
          console.log("catch-false");
          cb(false);
        });
    },
  },

my-input组件

input组件功能较为简单,主要是两个功能点

  • 实现v-model
  • blur和input事件触发校验
  • $attrs普通属性的传递
<template>
  <div>
    <input :type="type"
           :value="value"
           @input="onInput"
           @blur="onBlur"
           v-bind="$attrs" />
  </div>
</template>

<script>
export default {
  inheritAttrs: false,
  props: {
    type: {
      type: String,
      default: "text",
    },
    value: {
      type: String,
      default: "",
    },
  },
  methods: {
    onInput (e) {
      this.$emit("input", e.target.value);
      this.$parent.$emit("validate", e.target.value);
    },
    onBlur(){
      this.$parent.$emit("validate", this.value);
    }
  },
};
</script>

dispatch事件派发

input组件触发my-form-item的校验方法同样也会遇到form和item父子组件耦合的问题,他们的区别是form触发item组件校验事件是向下递归遍历寻找目标组件,触发目标事件,而input组件是向上寻找目标组件,触发目标事件。

继续回到emitter.js文件,实现dispatch方法

    dispatch(componentName, eventName, params) {
      let parent = this.$parent || this.$root;
      let name = parent.$options.componentName;
      while (parent && (!name || name !== componentName)) {
        parent = parent.$parent;
        if (parent) {
          name = parent.$options.componentName;
        }
      }
      if (parent) {
        parent.$emit.apply(parent, [eventName].concat(params));
      }
    },

用dispath方法改写this.$parent,解决父子组件耦合问题

  mixins: [emitter],
  methods: {
    onInput (e) {
      this.$emit("input", e.target.value);
      this.dispatch("my-form-item", "validate", e.target.value);
    },
    onBlur(){
      this.dispatch("my-form-item", "validate", this.value);
    }
  },

毛坯房验收

我们已经实现了一个极简版的form组件,和input组件,相当于建成了一个毛坯房,是时候验收了

验收清单

  • 事件广播和派发emitter.js

  • my-form组件

  • my-form-item组件

  • my-input组件

验收效果
在这里插入图片描述

  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2021-07-27 16:07:20  更:2021-07-27 16:07:51 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/4 15:49:33-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码