现在很多UI库都有很多的好用的组件库,那这写组件的设计原理也必须知道,今天实现一下自定义form表单组件
组件设计思想 :3个点
- 属性 :设计任何组件都需要传递一个属性
- 事件:组件内部对外通知的机制
- 组件可以扩展:组件内部不能写死,比如弹窗,不能只放按钮和文本,应该是用户想放什么放什么 ,slot插槽,需要分层 。
组件化设计思想实现
-
采坑 :
-
v-model 双向绑定表单元素的数据:实质是绑定了value、checked、selected属性
-
父组件向子组件传递数据 , 可以使用 :name 进行传递 ,也可以直接绑定属性 target=”yuhior”,那么这样传递的就是yuhior的值 不是变量
<Cart :name="name" :cart="cart" target="yuhior" ></Cart> <!--使用:name这种形式进行数据的--> <script> import Cart from './components/Cart' export default { name:'app', //组件的名字 components:{ Cart }, data(){ return{ name:'开课吧', cart:[] } }, } </script> 复制代码
-
父组件是使用这个子组件的组件 ,而不是template里嵌套子元素的是父组件,比如说这里KInput、FormItem、KForm的父组件都是App.vue
<!--App.vue--> <template> <div> <k-form :model="ruleForm" :rules=" rules"> <form-item label="用户名" prop="name"> //父组件这是 *****理解错误 <k-input v-model="ruleForm.name" name="name"></k-input> //开始我以为子组件 </form-item> <form-item label="年龄" prop="age"> <k-input v-model="ruleForm.age" name="age"></k-input> </form-item> </k-form> </div> </template> <script> import KForm from './components/Form'; import FormItem from './components/FormItem'; import KInput from './components/KInput'; export default { name: 'app', components:{ KForm, FormItem, KInput }, data(){ return{ } } } } </script> 复制代码
-
v-model 是一个特殊的属性绑定,相当于绑定了:value和@input两件事情
<custom-input v-model="searchText"></custom-input> <custom-input :value="searchText" @input="searchTex=$event" ></custom-input> 复制代码
-
-
form表单验证的设计实现
表单组件进行分层:
KForm组件:负责接收自定义的规则
KFormItem:负责显示错误信息也就是规则的验证
KFromInput:负责数据的双向绑定
-
子组件向父组件传递数据,通知父组件组件内部变化
//子组件 <template> <button @click="handleClick"> 开课吧 </button> </template> <script> export default { methods: { handleClick(event){ this.$emit('toMessage',event); }, } } </script> 复制代码
// 父组件接收 <template> <k-input v-on:toMessage="toFuzhi"></k-input> //toMessage 是子组件那个出发事件 </template> <script> export default { methods: { toFuzhi(msg){ this.msg = msg //进行父组件的复制 }, } } </script> 复制代码
-
先看看平常使用: 和lelement类似
<template> <k-form :model="ruleForm" :rules="rules"> <k-form-item label="用户名" prop="name"> <k-input v-model="ruleForm.name" name="name"></k-input> </k-form-item> <k-form-item label="年龄" prop="age"> <k-input v-model="ruleForm.age" name="age"></k-input> </k-form-item> </k-form> </template> <script> import KForm from './components/KForm'; import KFormItem from './components/KFromItem'; import KInput from './components/KInput' export default { name: 'app', components:{ KForm, KFormItem, KInput }, data() { return { ruleForm:{ name:'', age:'' }, rules: { name:{required: true, message: '用户名不能为空!'}, age: {required: true, message: '年龄不能为空!'} } } } } </script> 复制代码
-
KFormInput 实现: 需要注意 理解 this.$emit 传值的时候,因为 父组件 v-model 绑定了:value和@input两件事情 ,所以 使用的时候直接 v-model =“ruleForm.age” 就相当于 @input = ‘’ ’‘ 所以也就接收到了
<template> <!--实现v-model--> <input type="text" :value="inputText" @input="handleInput" @blur="handleBlur" /> </template> <script> export default { name: "KInput", props:{ value:{ type:String, default:'', require: true }, name:{ type:String } }, data(){ return{ inputText:this.value // * 双向输入的输入->父元素传递过来的 } }, methods: { handleInput(e) { let value = e.target.value; // props 是单向数据流传递数据,所以修改值之后,props的值不变,父元素的值不改变 this.inputText = value; // 当输入框输入值的时候,获取输入框的值 // * 双向数据的输出-> 通知父元素进行回流绑定 this.$emit('input',value); // 子组件向父组件传递数据 使用this.$emit('input') input事件触发 父组件使用@input接收 this.$bus.$emit('KFormItem', value); //将值通过总线机制,实现KInput和KFormItem不相关的组件的数据传递,传递给KFromItem值,它进行验证 }, handleBlur(){ //失去焦点是进行验证 let value = this.inputText; this.$bus.$emit('KFormItem',value,); } } } </script> 复制代码
-
KFormItem 实现 :
-
我们接收到KInput发送过来的数据 , 然后进行 验证
//----------KFromItem接收 created() { this.$bus.$on('KFormItem', (value) => { //接收KInput组件传递过来的值 进行调用validate方法进行验证 this.validate(value); }) }, 复制代码
-
需要验证 就需要 获取验证的规则
//-----------KForm发送数据实例 provide(){ return { kform:this } }, //-----------KFormItem接收 inject:['kform'], // 2.接收KForm车通过 provide传递过来的 组件实例 这样才能获取到规则 //-----------获取规则 methods:{ getRules(){ //获取规则的方法 let formRules = this.kform.rules[this.prop]; //找到对应的 rules.prop的值 return formRules; }, } 复制代码
-
获取了规则 就需要写 校验方法
validate(obj){ //1.验证我需要获取验证规则 ,也就是使用组件使用时传递进来的规则 const rule = this.getRules(); //3.调用获取rules const value = this.kform.model[this.prop]; //4.由于在KInput组件中我们已经将输入框的变化值使用this.$bus.$emit通知给父组件了,所以这样获取输入框的值 if (rule.required&&!value){ //5.进行验证 ,require为true ,!value为真(value的值为空)就说明验证未通过 this.validateStatus = 'error'; this.errorMessage = rule.message }else{ //6.验证通过 this.validateStatus = 'stating' //验证状态为通过状态 } } 复制代码
-
-
KForm实现就简单了:给一个槽口 让KFormItem来占用就行了
<template> <form> <!--子元素的插口--> <slot></slot> </form> </template> <script> export default { name: "KForm", provide(){ //给KFromItem传递规则 数据 return { kform:this } }, // provide:{ // kform:this ; 也可以使用这种方式进行数据传递 ,但是我们现在需要返回this实例 所以需要使用provide:function(){}形式 但是如果返回变量就可以直接返回 // }, props:{ model:{ type:Object }, rules:{ type:Object } } } </script> 复制代码
-
-
完成之后发现 不管输入哪一个输入框 都验证所有的验证框都响应,进行验证
这是由于 我们再KInput.vue中使用 的.on都是全局的,所以父组件接收也就是@input =“ ”的时候都接收到了变化,也就是多个KForm监听@input事件 ,所以多个验证都响应 ,
那么我们就判断一下
// --------在KFormInput的$bus.$emit 发送值的时候不仅仅发送一个value ,我们发送一个对象 this.$bus.$emit('KFormItem',{ value, name:this.name }); // --------在KFormItem验证中判断一下,如果发送过来的name和这个prop不相等,说明不是当前输入的input if (obj.name!==this.prop) { return //别的prop的 如果不是这个name则直接返回 } 复制代码
-
规则扩展
-
但是如果我们想有多个验证规则怎么办 ?如下
rules: { name: [ //如果有多个校验项 ,则验证需要修改 {required: true, message: '用户名不能为空!'}, {minLength: 3, message: "用户名长度要大于3"}, {maxLength: 10, message: "用户名长度要小于10"}, ], age: {required: true, message: '年龄不能为空!'} } 复制代码
-
那就在验证里边加判断被!!!
//7.如果验证规则有多个,那就需要判断 if (Array.isArray(value)){ value.forEach(v=>{ if(rule.required && !value){ } if(rule.mixLength && value.length<rule.mixLength){ } }) } 复制代码
-
-
[源码]: