戳蓝字「前端技术优选」关注我们哦!

本文介绍了从0到1实现的表单组件的封装,用到了 TypeScript + Vue3.0的技术,如果读者不是很熟悉的话,可以先看下这两篇博客 https://juejin.cn/post/6903135012963483656 和 https://juejin.cn/post/6908185323801575432
做出来的效果:

bootstrap

我们直接使用 bootstrap 的精美样式。首先在通过 Vue-Cli 安装的项目中安装 bootstrap。Vue-Cli初始化项目的方法 https://juejin.cn/post/6908185323801575432 有介绍,这里不再赘述。

npm install bootstrap@next --save

vetur

为了能在 vue 文件的 template 模板中使用语法提示,我们需要修改 vetur 的 settings.json 配置。新增 "vetur.experimental.templateInterpolationService": true




v-model原理

Vue2.0

在 Vue2.0 中,对于有 input 事件的标签(包括 , , 或 ),我们可以直接使用 v-model 进行双向数据绑定,即当用户修改表单内容的时候,v-model 绑定的数据可以自动跟着修改。

<template>    <div>        <input v-model="msg"/>        <p>{{msg}}p>        <button @click="change">changebutton>    div>template><script>export default {name: "count",      data(){return {msg: ""          }      },methods: {          change(){this.msg = "Hello Vue";          }      }  }script>

v-model 的本质是语法糖,它负责监听用户的输入事件以更新数据,并对一些极端场景进行了一些特殊处理。v-model 在内部为不同的输入元素使用不同的 property 并抛出不同的事件:

  • text 和 textarea 元素使用 value property 和 input 事件;
  • checkbox 和 radio 使用 checked property 和 change 事件;
  • select 字段将 value 作为 prop 并将change作为事件。翻译一下:
<input v-model="msg"/>//等价于<input :value="msg" @input="msg=$event.target.value"/>
<input type="checkbox" value="Java" id="Java" v-model="msg">//等价于<input type="checkbox" value="Java" id="Java" :checked="msg" @change="changeInput">//其中changeInput函数要根据msg的类型做不同处理
<select v-model="msg">    <option value="dog">dogoption>    <option value="cat">catoption>    <option value="Hello Vue">Hello Vueoption>select>//等价。<select :value="msg" @change="changeSelect">    <option value="dog">dogoption>    <option value="cat">catoption>    <option value="Hello Vue">Hello Vueoption>select>

知道了 v-model 本质以后,我们就可以为 自定义组件 定义 v-model。如下:

<myComponent v-model="msg">myComponent>//等价于<myComponent :value="msg" @input="msg=arguments[0]">myComponent>

在组件 myComponent 中,通常会定义一个变量接收 value 的变化;并且修改这个变量的时候去 $emit 触发父组件订阅的 input 事件。

<template>    <div>        {{newVal}}        <button @click="change">点击button>    div>template><script>export default {name: "my-component",props: {value: {type: String,default: "Vue"        }    },watch: {        value(val){this.newVal = val;        }    },    data(){return {newVal: this.value        }    },methods: {        change(){this.newVal = "我是啊";this.$emit("input", this.newVal);        }    }}script>

Vue3.0

子组件中统一使用 modelValue 传入初始值,并通过 emit 出发 update:modelValue 事件。看例子:

//ValidateInput.vue<template>    <input :value="inputRef.val" @input="updateValue">template><script lang="ts">import { defineComponent, reactive } from 'vue';export default defineComponent({props: {modelValue: String    },    setup(props, context){const inputRef = reactive({val: props.modelValue || ""        });//监听 props.modelValue         watch(() => props.modelValue, (newVal) => {            inputRef.val = newVal as string        })const updateValue = (e: KeyboardEvent)=>{const targetValue = (e.target as HTMLInputElement).value;            inputRef.val = targetValue;            context.emit('update:modelValue', targetValue);        }return{            inputRef,            updateValue        }    }})script>

在父组件中使用即可:

<ValidateInput v-model="msg"/>

那么绑定的 msg 会随着 input 内容的改变而改变,可以实现数据双向绑定。

校验机制

表单的校验就是在用户输入的时候判断输入的内容是否合法,不合法的时候实时在表单下面进行错误信息提示。那么就需要在上面定义的组件上面新增一个属性 rules, 用来传递用户对当前表单元素的规则;然后在 blur 的时候,遍历规则rules校验输入的内容。以邮箱输入框校验为例说明。

<template>    <div class="validate-input-container pb-3">        <input type="text" class="form-control" :class="{'is-invalid': inputRef.error}":value="inputRef.val" @input="updateValue" @blur="validateInput">        <span v-if="inputRef.error" class="invalid-feedback">{{inputRef.message}}span>    div>template><script lang="ts">const emailReg = /^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/import { defineComponent, reactive, PropType } from 'vue';interface RuleProp{type: 'required' | 'email' | 'custom';    message: string;    validator?: () => boolean;}export type RulesProp = RuleProp[];export default defineComponent({props: {modelValue: String,rules: Array as PropType    },    setup(props, context){const inputRef = reactive({val: props.modelValue || "",error: false,message: ""        });const updateValue = (e: KeyboardEvent)=>{const targetValue = (e.target as HTMLInputElement).value;            inputRef.val = targetValue;            context.emit('update:modelValue', targetValue);        };const validateInput = () => {if(props.rules){const allPassed = props.rules.every((rule: RuleProp) => {let passed = true;                    inputRef.message = rule.message;switch(rule.type){case 'required':                            passed = (inputRef.val.trim() !== '')break;case 'email':                            passed = emailReg.test(inputRef.val)break;case 'custom':                            passed = rule.validator ? rule.validator() : truebreak;default: break;                    }return passed                })                inputRef.error = !allPassed;            }return true        }return{            inputRef,            updateValue,            validateInput        }    }})script>

为了使用的时候能将 placeholder 属性绑定到 input 标签上。这里需要做 2 步:(1)在 InvalidateInput 组件中,设置 inheritAttrs: false 表示你不希望组件的根元素继承 attribute; (2)在组件中绑定 $attrs

export default defineComponent({   //新增    inheritAttrs: false,    setup(props, context){     ...    }
<input type="text" class="form-control" :class="{'is-invalid': inputRef.error}":value="inputRef.val" @input="updateValue" @blur="validateInput"v-bind="$attrs">

更多详情可以参考 https://cn.vuejs.org/v2/guide/components-props.html#%E7%A6%81%E7%94%A8-Attribute-%E7%BB%A7%E6%89%BF

PropType

补充一个Vue3.0的知识点 PropType。我们知道组件的 props 可以用来定义组件可以传递的参数。格式如

props: { rules: Array}

在变量 rules 后面需要使用一个构造函数作为它的类型。这个时候为了使用 TypeScript 智能提示的好处,我们通常会定义数组里面每项的数据类型,这个时候用到了 TypeScript 中的 interface 和 type,为了将定义的类型和构造函数结合,Vue3.0 中提供了 PropType,用来将构造函数和特定类型结合起来。使用方法如上面使用的:

interface RuleProp{    type: 'required' | 'email' | 'custom';    message: string;    validator?: () => boolean;}export type RulesProp = RuleProp[];export default defineComponent({    props: {        modelValue: String,        rules: Array as PropType    }})

ValidForm 封装

在 ValidateForm.vue 中对外暴露了 submit 表单提交事件;同时使用 slot 可以定制 Form 表单每项的内容,以及提交按钮的样式;最后我们监听的是最外层div的click事件,这样用户点击提交的时候可以触发表单的提交操作。

//ValidateForm.vue<template>  <form class="validate-form-container">    <slot name="default">slot>    <div class="submit-area" @click.prevent="submitForm">      <slot name="submit">        <button type="submit" class="btn btn-primary">提交button>      slot>    div>  form>template><script lang="ts">import {defineComponent} from 'vue'export default defineComponent({emits: ['submit'],    setup(props, context){const submitForm = function(){console.log("click");            context.emit("submit", false)        }return{            submitForm        }    }})script>

组件通信

我们知道对于父组件可以通过 props 向子组件传递数据;子组件可以通过 emit 事件向父组件传递数据。或者通过 、children 获取响应的组件信息。在 Vue2.0 中,对于使用了插槽 slot 的父子组件通信只能借助事件总栈 EventBus 来实现了;在 Vue3.0 中,推荐使用一个插件 mitt 来实现事件的发布订阅。首先先安装 mitt

npm install mitt --save

首先整理一下思路:为了在 ValidateForm 组件中访问到 ValidateInput 每一个子组件的校验方法。我们需要在 ValidateForm 组件中订阅 form-validate 事件, 回调函数 callback 用来收集每一个子组件的校验方法;在子组件中,组件需要在初始化的时候发布 form-validate 事件,并将自己的校验函数传递进入。这样在用户点击 submit 按钮的时候,我们就可以在 ValidateForm 组件中触发每一个子组件(ValidateInput )的校验方法。从而实现了表单的校验。

另外还有2点需要注意:(1)是由于订阅发布是有顺序的,应该是父组件先订阅,然后子组件发布这样才能让父组件订阅到。所以在 ValidateForm 中我们可以在 setup中订阅form-validate 事件;而在ValidateInput 中在 onMounted 的时候发布form-validate 事件。(2) 最后的善后工作也要做好。我们需要在 而在ValidateInput onUnmounted的时候取消订阅的事件,并将存储子组件事件的数组清空。

//ValidateForm.vue

表单组件_从0到1封装表单组件(TypeScript + Vue3.0 版)相关推荐

  1. vb表格控件_(超级干货)ExcelVBA拆分表格并分别发送邮件增强版

    这是POINT小数点的第 339 篇文章 点点写在前面: 之前我们有分享过一个场景1:你制作了一份总表你想要拆分成各个分公司,并且你需要对分公司的多个同事发送邮件.如果有20几个分公司,你要拆分+写邮 ...

  2. python线性表顺序存储实现_数据结构——基于C的线性表的顺序存储结构的基本操作的实现...

    /*** *SeqList.c *Copyright (c) 2015, XZG. All rights reserved. *Purpose: * 线性表顺序存储结构的创建.数据插入.数据获取.获取 ...

  3. ajax form表单提交_开发日志:金数据表单自动提交脚本

    最近学校要求我们每天通过一个在线表单打卡自己在家做的体育课项目,在提交的时候我突然想了下如果能有一个自动的系统每天帮我自动打卡岂不是能省很多时间?而且我一直很想学Python的网络爬虫以及服务器后端的 ...

  4. camunda流程定义表无数据_创建流程实例时 act_ru_identitylink 表中没有出现相关的人员数据...

    老师您好,我对流程实例有两个问题: 创建流程实例的方法,视频中给出的是 ProcessInstance processInstance = runtimeService.startProcessIns ...

  5. mysql 主表存hash和子表的名字_【mysql】mysql分表和表分区详解

    为什么要分表和分区? 日常开发中我们经常会遇到大表的情况,所谓的大表是指存储了百万级乃至千万级条记录的表.这样的表过于庞大,导致数据库在查询和插入的时候耗时太长,性能低下,如果涉及联合查询的情况,性能 ...

  6. mysql慢查询 表级锁_三分钟了解Mysql的表级锁——《深究Mysql锁》

    延伸阅读: 五分钟了解Mysql的行级锁 一分钟深入Mysql的意向锁 mysql锁相关讲解及其应用--<深究mysql锁>了解锁前,一定要先看这篇,了解什么是MVCC,如果我们学习锁,没 ...

  7. mysql表空间大小_浅谈mysql中各种表空间(tablespaces)的概念

    mysql中,会涉及到各种表空间的概念,虽然,很多方面这些概念和Oracle有相似性,但也有很多不同的地方,初学者很容易被这些概念弄的晕头转向,从而,混淆这些概念的区别和理解,下面,就简要介绍和说明一 ...

  8. mysql 变量作表名查询_使用MySQL函数变量作为表名查询

    我需要有一个表中增加一定的ID(如AUTO_INCREMENT)函数使用MySQL函数变量作为表名查询 我有水木清华这样 DELIMITER $$ DROP FUNCTION IF EXISTS `G ...

  9. mysql 表设计工具_非常好用的一个表设计工具(EZDML)

    表结构设计器(EZDML) 这是一个数据库建表的小软件,可快速的进行数据库表结构设计,建立数据模型.类似大家常用的数据库建模工具如PowerDesigner.ERWIN.ER-Studio和Ratio ...

  10. uniapp+typeScript+vue3.0+vite

    最近公司需要开发新版小程序,思考了一下,决定还是用最新的技术进行开发,同时也能锻炼到自己,废话不多说,开搞: 一.首先打开uniapp的官网:uni-app官网 //环境安装 //全局安装vue-cl ...

最新文章

  1. python 正则 去除字符串中异常字符
  2. 抖音:我“弱”我有理
  3. jQuery remove 内存 释放
  4. 变压器耦合和电容耦合_超越变压器和抱抱面的分类
  5. 您不能不知的ToString()方法
  6. 我的github网址链接
  7. android APP优化知识图谱
  8. 梯度下降和delta法则
  9. MySql免安装版绿化版安装配置,附MySQL服务无法启动解决方案
  10. lock concurrence
  11. 网络安全系列-XI: 主流网络协议介绍
  12. Google SketchUp For Dummies
  13. 网络安全笔记-99-渗透-SSRF
  14. EAAccessory iphone与经过苹果MFI授权认证的硬件通讯
  15. 四路监控物联卡赋能卡友行车安全
  16. Oracle HINT的常见用法
  17. js中match函数的用法
  18. 6_计算机网络_应用层-HTTP-DNS-跳板机
  19. Python模块之二:Python3 常用模块总结
  20. win10桌面能不能写待办事件清单?

热门文章

  1. 基于python 实现KNN 算法
  2. JQuery canvas 验证码
  3. HP数组转JSON函数json_encode和JSON转数组json_decode函数的使用方法
  4. 修剪花卉(codevs 1794)
  5. 111... 南邮NOJ 1079
  6. iVIEW: An Intelligent Video over InternEt and Wireless Access System
  7. 以算法岗为例:我最想对入职前的自己说些什么?
  8. 【ACL2020】基于语境的文本分类弱监督学习
  9. 3月面经汇总-字节跳动,美团,腾讯算法岗
  10. 入门 | 神经网络词嵌入:如何将《战争与和平》表示成一个向量?