介绍
本文目的是对Vue组件化内容在网课学习的总结和复习。多有不足之处,请多多交流。
承接上文(Vue组件化总结),学习了Vue组件化原理,自然是要来一波实战练习的。在网课上面,老师以element-ui的form表单为蓝本,从0 到1的讲述了如何自行设计一个表单组件。其内容包含输入框组件、表单项组件、表单组件以及简单的表单数据校验功能。
组件基础实现
输入框组件
el-input分析
先看下el-input组件的使用方式,再进行分析
<el-input v-model="input" placeholder="请输入内容"></el-input>
以上代码是el-input组件的简单使用形式,由此可以分析得出:
- el-input组件内部有原始的input输入框
- el-input组件拥有双向绑定值input
- el-input组件上的placeholder等属性是可以传递给其内部的input输入框使用的
组件使用v-model
PS:这里是对以上分析的第二点进行补充,在组件使用v-model双向绑定,其内部原理是什么?
Vue的官方文档中是这样解释的:组件上的 v-model
默认会利用名为 value
的 prop属性 和名为 input
的事件(仅指输入框组件,下拉框等另有说明)。
所以,上文的写法相当于在el-input组件中的input上绑定了value和input事件。
mine-input实现
根据上述分析,我们实现出来的代码:
<template>
<div>
<input :value="value" @input="onInput" v-bind="$attrs" />
</div>
</template>
<script>
export default {
inheritAtrrs: false,
props: {
value: {
type: String,
default: "",
},
},
methods: {
onInput(e) {
this.$emit("input", e.target.value);
},
},
};
</script>
在我们实现的组件中,
-
通过props属性传递进来了v-model绑定的value属性,并声明了input事件的触发方法onInput。
- 这部分实现在官方文档中有
-
$attrs和inheritAttrs的配合使用是为了接收type、placeholder等属性。
- 在组件使用处传递的数据,如果没有在内部的props属性接收范围内,都在$attrs里面。
- 设置inheritAttrs:false的原因是,防止父组件传递的数据挂载到子组件的根元素上
mine-input使用
<mine-input v-model="model.username"></mine-input>
<mine-input type="password" v-model="model.password"></mine-input>
这样的使用效果就和el-input的效果差不多了。
表单项组件
el-form-item分析
先看下el-form-item组件的使用方式,再做分析
<el-form-item label="活动名称" prop="name">
xxx
</el-form-item>
可以知道的是:
- el-form-item组件内部接收label、prop等参数
- el-form-item组件内部有标签可以显示label内容和错误提示内容
- el-form-item组件内部有匿名插槽,可以显示el-input等组件内容
mine-form-item实现
根据上述分析,我们实现出来的代码:
<template>
<div>
<label v-if="label">{{ label }}</label>
<slot></slot>
<p v-if="error">{{ error }}</p>
</div>
</template>
<script>
export default {
props: {
label: {
type: String,
default: "",
},
prop: {
type: String,
default: "",
},
},
data() {
return {
error: "",
};
},
};
</script>
在我们实现的组件中
- props属性中接收了传递进来的label和prop属性。
- label标签用于显示label参数的值;p标签用于显示错误提示
- slot插槽用于显示min-form-item中的内容
mine-form-item使用
<mine-form-item label="用户名" prop="username">
<mine-input v-model="model.username"></mine-input>
</mine-form-item>
<mine-form-item label="密码" prop="password">
<mine-input type="password" v-model="model.password"></mine-input>
</mine-form-item>
这样的使用效果就和el-form-item的效果差不多了。
表单组件
el-form分析
先看下el-form组件的使用方式,再做分析
<el-form :model="ruleForm" :rules="rules" ref="ruleForm">
xxx
</el-form>
可以知道的是:
-
el-form组件内部可以接收model和rules属性值
-
el-form组件内部有匿名插槽,用于显示内容元素
-
ref标记用于在表单提交方法里获取表单组件实例
- ref作用于组件上,则引用指向组件实例
- ref作用于dom元素上,则引用指向dom元素
mine-form实现
根据上述分析,我们实现出来的代码:
<template>
<div>
<form>
<slot></slot>
</form>
</div>
</template>
<script>
export default {
props: {
model: { type: Object, required: true },
rules: { type: Object },
},
};
</script>
在我们实现的组件中
- props里接收了model和rules
- 表单的form标签内部使用slot标签显示组件内容
mine-form使用
<mine-form :model="model" :rules="rules" ref="loginForm">
<mine-form-item label="用户名" prop="username">
<mine-input v-model="model.username"></mine-input>
</mine-form-item>
<mine-form-item label="密码" prop="password">
<mine-input type="password" v-model="model.password"></mine-input>
</mine-form-item>
<mine-form-item>
<button @click="submitForm">提交</button>
</mine-form-item>
</mine-form>
这样的使用效果就和el-form的效果差不多了。
表单校验
经过以上的分析和实现,我们把表单组件的基础架构搭建了起来,但是表单最重要的一个功能还没实现,那就是表单校验功能。
老规矩,先分析一下整体的思路:
-
form组件接收model和rules,但是内容全在匿名插槽里,所以表单项的具体校验不会放form组件里执行,而是在form-item组件中执行。而表单组件里只对form-item的校验结果进行汇总,并给出最终的校验结果。
- form组件需要通过某种方式将model和rules的内容传递给form-item组件,以便于form-item组件通过prop属性查找当前表单项的值和校验规则
- 通过provide/inject方式可以将自身this传递到子代组件中
-
form-item组件内可以找到当前项的具体数据和规则,那么数据校验应该是在这一级的组件里进行
- form-item接收到form传递过来的this,可以通过this.form.model和this.form.rules以及自身的prop属性可以找到当前项的rule和value;
- 再通过async-validator插件校验方法,并返回校验结果true、false
-
input组件需要在合适的地方以合适的方式触发校验方法
- 通过parent/child的方式调用父组件(form-item组件)的方法。
mine-input触发校验
由于我们实现的效果很简单,所以可以在mine-input组件的input事件中触发校验
methods: {
onInput(e) {
this.$emit("input", e.target.value);
this.$parent.$emit("validate");
},
},
$emit的使用方式
第一种
子组件通过this.$emit代用父组件方法,父组件在调用子组件时做监听
// 子组件
<input v-model="value" @input="input"/>
export default {
methods:{
input(e){
this.$emit('change',e.target.value)
}
}
}
// 父组件
<cart @change="handleChange"></cart>
export default {
methods:{
handleChange(data){
console.log(data)
}
}
}
这也是我们平时最常用的方式,但是在这里明显的不适用。
第二种
子组件通过this.parent.parent.parent.emit调用父组件方法,父组件在创建时通过this.$on做监听
// 子组件
<input v-model="value" @input="input"/>
export default {
methods:{
input(e){
this.$parent.$emit('change',e.target.value)
}
}
}
// 父组件
<cart></cart>
export default {
mounted(){
this.$on("change",data=>{
this.handleChange(data)
})
},
methods:{
handleChange(data){
console.log(data)
}
}
}
这就是我们在实现的代码里使用的方式
第三种
父组件通过props形式将方法传递给子组件,子组件内接收到该方法后,可以直接调用
mine-form-item执行校验
min-form-item组件在mounted钩子里进行监听
import Validator from "async-validator";
inject: ["form"],
mounted() {
this.$on("validate", () => {
this.validate();
});
},
methods: {
validate() {
const rules = this.form.rules[this.prop];
const value = this.form.model[this.prop];
const validator = new Validator({ [this.prop]: rules });
return validator.validate({ [this.prop]: value }, (errors) => {
if (errors) {
this.error = errors[0].message;
} else {
this.error = "";
}
});
},
},
安装async-validator
async-validator也是element官方使用的的表单校验插件
安装命令:
npm i async-validator -S
validator方法
在该方法内部:
- 首先通过inject方式接收到form实例
- 根据自身props传递的prop属性找到当前值value和当前规则rule
- 创建校验对象
- 执行校验对象的校验方法,并给出校验结果和信息提示
mine-form提交前校验
from组件内创建validate方法,以供通过ref指向方式调用
form组件校验方法
validate(cb) {
const results = this.$children
.filter((item) => item.prop)
.map((item) => item.validate());
Promise.all(results)
.then(() => cb(true))
.catch(() => cb(false));
},
- 通过this.$children获取到子组件数组形式的集合
- 通过链式操作获取有prop属性的表单项,并执行对应的校验方法
- promise.all()返回是否全部校验通过的结果。
promise.all方法
Promise.all可以将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值。
调用form组件校验方法
submitForm() {
this.$refs.loginForm.validate((valid) => {
console.log(valid);
});
},
以上就是我在网课学习的组件化实战内容。