进阶会发现越往前走,知道的越多,不知道的也越多,但是通过学习,弱化知识盲区,对自己的实际开发选择会有很大的影响,也会影响变成思维。
概述
组件系统是vue的一个重要概念,因为它是一种抽象,让我们可以使用独立可抽象的组件来构建大型应用,任意类型的应用界面都可以抽象为一个组件树。所谓组件化,就是把页面拆分成多个独立的组件,每个组件依赖的 css js 模板 图片 等资源等放在一起开发和维护,所以组件化在系统内部可复用,组件之间可以嵌套。因此组件化能提高开发效率,方便重复使用,简化调试步骤,提升项目可维护性,编译多人协同开发
组件化实战动手封装表单组件
注意
高内聚 低耦合 封装通用组件每一个组件的功能尽量单一 明确,比如 input 就实现双向数据绑定 收集数据
需求分析
首先要封装一个简单的类似Element UI form 表单的组件 根据Element form 的组成 划分三各需要自定义封装的组件,主要功能是 收集数据 并进行数据的校验。
- ZForm :指定数据 和 数据校验规则
- ZFormItem:执行校验 显示对应的错误提示信息
- ZInput :维护数据
完成了需求分析 就去解决每一个需求的功能实现吧 ,从内层到外层实现 能最快的看到效果并方便调试
代码实现
首先来看下实现后的使用如下
<template>
<div>
<h1>Form 自定义通用组件练习 </h1>
<ZForm :model="userInfo"></ZForm>
<ZFormItem label="用户名">
<ZInput v-model="userInfo.username"></ZInput>
</ZFormItem>
{{userInfo.username}}
</div>
</template>
<script>
import ZForm from "./ZForm";
import ZFormItem from "./ZFormItem";
import ZInput from "./ZInput";
export default {
data() {
return {
userInfo: {
username: "tom",
password: ""
},
rules: {
username: [{ required: true, message: "请输入用户名称" }],
password: [{ required: true, message: "请输入密码" }]
}
};
},
components: {
ZInput,
ZFormItem,
ZForm
}
};
</script>
<style lang="scss" scoped>
</style>
\ No newline at end of file
1、ZInput :实现双向数据绑定 v-model
自定义组件 如果想实现双向数据绑定 需要两步实现 value 属性的绑定 和 input 事件 :value @input 实现
除了value值等 可能还会给ZInput 组件传很多其他的值 ,要设置到当前的input 组件上,比如placeholder 因此可以使用$attrs 进行属性设置,但是该属性会默认也设置到整控件的根组件上 比如 div ,可以通过设置 iinheritAttrs: false 避免其他组件的属性继承。
<template>
<!-- <div> -->
<!-- 自定义组件 如果想使用双向数据绑定 实现两步 value 属性绑定和input 事件 :value @input -->
<input :type="type" :value="value" @input="onInput">
<!-- </div> -->
</template>
<script>
export default {
props: {
value: {
type: String,
default: ""
},
type: {
type: String,
default: "text"
}
},
data() {
return {};
},
created() {
console.log(this.value, this.type);
},
methods: {
onInput(e) {
// 一般来说 value 数据是单项数据流 也就是监听到数据变化之后不要直接修改,要直接抛出一个事件让他去修改
// 因此在onInput 中只需要取派发一个事件就可以了 这样便形成了单项数据流
this.$emit("input", e.target.value);
}
}
};
</script>
<style lang="scss" scoped>
</style>
2、ZFormItem :实现校验 及 校验结果信息的显示 因为内部要包含 ZInput 所以 要使用插槽 slot ,同时根据实际需求也可以留出label的位置 代码如下:
<template>
<div>
<!-- label -->
<label v-if="label">{{label}}</label>
<slot></slot>
<!-- 校验信息提示 -->
<p v-if="error">{{error}}</p>
</div>
</template>
<script>
export default {
props: {
label: {
type: String,
default: ""
}
},
data() {
return {
error: "" // error 为空说明校验通过
};
}
};
</script>
<style lang="scss" scoped>
</style>
\ No newline at end of file
3、ZForm 是最外层的爸爸 ,要提供两个数据 model 和 校验规则rules
<template>
<div>
<slot></slot>
</div>
</template>
<script>
export default {
data() {
return {};
},
props: {
model: {
type: Object,
required: true
},
rules: {
type: Object
}
}
};
</script>
<style lang="scss" scoped>
</style>
以上代码完成了三个组件的基本准备工作,接下来是核心的业务逻辑实现 数据收集 和 数据校验
先考虑一下几个问题:
Q:最外层的数据 怎么传给子孙后代呢 ?
A:参考组件通讯的方法 https://blog.csdn.net/Sandy_zhi/article/details/108604466 可以选择provide 和 inject 注入进行实现
Q:数据传递应该传递什么呢? userInfo ? rules ?
A:经过综合分析,在使用过程中可能不仅仅是这两个数据,因此可以将组件的实例 form 传给子孙后代 ,想用什么直接从组件本身取就可以了 Element UI form 也是这样实现的。
Q:关于校验 哪个组件去提交和通知校验
A:ZInput 去通知提交校验,因为知道数据变化的是ZInput 组件
Q:每一个ZFormItem 组件怎么知道当前的校验规则和校验内容
A:需要在ZFormItem 上设置prop 属性,然后从model 和 rules 取出内容 和 规则 再执行校验
Q:怎么实现全局校验,即在我们通常点击提交按钮的时候,要有个全局的校验
A:需要对最外层的表单也就是ZForm 设置一个全局校验的方法 ,该方法遍历获取设置了校验的孩子 并调用孩子的校验方法。
注:demo中的校验用到了第三方的校验库 :async-validator
核心代码如下:
① ZInput 中通知父组件进行校验
// 通知父亲级别通知校验
this.$parent.$emit("validate"); // 虽然有耦合问题
② ZFormItem 校验核心代码
validate() {
//规则
// console.log(this.from.rules[this.prop]);
const rules = this.form.rules[this.prop];
// 当前值
// console.log(this.form.model[this.prop]);
const value = this.form.model[this.prop];
// 校验描述对象 校验源 校验规则
const desc = { [this.prop]: rules };
// 创建Schema 的实例 根据描述创建 Schema
const schema = new Schema(desc);
return schema.validate({ [this.prop]: value }, errors => {
if (errors) {
console.log(errors);
this.error = errors[0].message;
// message 就是我们校验规则的是message
} else {
this.error = "";
}
});
}
③ ZForm 全局校验方法
validate(cb) {
// 获取所有孩子的ZFormItem 并调用每一个的validate 方法
//[resultPromise]
const tasks = this.$children
.filter(item => item.prop) // 过滤掉没有prop属性的item
.map(item => item.validate());
// 统一处理所有Promise结果
Promise.all(tasks)
.then(() => cb(true))
.catch(() => cb(false));
}
④index 页面登录按钮吊起全局校验
login() {
//进行全局校验
this.$refs["loginFormRef"].validate(valid => {
console.log("valid valid valid " + valid);
const notice = this.$create(Notice, {
title: "我是小智,请求登录",
message: valid ? "登录成功!" : "校验失败!",
duration: 2000
});
notice.show();
// if (valid) {
// alert("submit");
// } else {
// console.log("error submit!");
// return false;
// }
});
}
Notice 弹窗的封装
弹窗或者通知类的组件都是独立于Vue组件之外的,通常挂载于body ,它们是通过JS 动态创建的,不需要再任何组件中声明,这样做不仅能够减少耦合,位置层级等也比较好控制
主要步骤:
①定义组件
②动态创建组件
③ 将组件挂载到body上
④ 记得销毁组件,不然会出现很多垃圾组件占用内存
核心代码:
import Vue from 'vue'
function create(Component, props) {
// 组件构造函数如何获取
//1、Vue.extend()
//2、render
const vm = new Vue({
//h 是createElement 返回VNode(虚拟dom)
// 需要挂载才能变成真实dom
render: (h => h(Component, {
props,
})),
}).$mount() //不指定宿主元素 则会创建真实dom 但是不会追加操作
// 获取真实dom
document.body.appendChild(vm.$el)
const comp = vm.$children[0]
// 删除
comp.remove = function () {
document.body.removeChild(vm.$el)
vm.$destroy()
}
return comp
}
export default create
Notice组件
<template>
<div class="box" v-if="isShow">
<h3>{{title}}</h3>
<p class="box-content">{{message}}</p>
</div>
</template>
<script>
export default {
props: {
title: {
type: String,
default: ""
},
message: {
type: String,
default: ""
},
duration: {
type: Number,
default: 1000
}
},
data() {
return {
isShow: false
};
},
methods: {
show() {
this.isShow = true;
setTimeout(this.hide, this.duration);
},
hide() {
this.isShow = false;
// 清除自己
this.remove();
}
}
};
</script>
<style>
.box {
position: fixed;
width: 100%;
top: 16px;
left: 0;
text-align: center;
pointer-events: none;
background-color: #fff;
border: grey 3px solid;
box-sizing: border-box;
}
.box-content {
width: 200px;
margin: 10px auto;
font-size: 14px;
padding: 8px 16px;
background: #fff;
border-radius: 3px;
margin-bottom: 8px;
}
</style>
以上就完成了此次form表单的实战之旅了,主要是用到了插槽,组件间的通信 一些属性,开拓了自己的编程思维,以上代码样式和耦合性的问题还没有处理,期待下次进阶了。
完整代码地址
https://gitee.com/xiaozhidayu/vue-study-component
https://gitee.com/xiaozhidayu/vue-study-component.git
只要持续地努力,不懈地奋斗,就没有征服不了的东西。