>[success] # 利用通信 仿写一个form 通信
~~~
1.自定义个组件之间传值就不能再使用'bus'和'vuex' 等系列,
因为'bus' 和 'vuex' 都是项目级别的传递,自定义组件无法使用
变依赖'vuex' 或者'bus',因此需要使用组件通信的其他几种特殊传递
2.第一种常见的父子传递'props' 父传子,'$emit' 控制父组件的某个方法,
让控制父组件的某个方法将从子组件传递过来的值重新赋值形成'子传父'。
3.第二种'vue' api文档中的'provide' / 'inject' ,'提供'/'注入',
这种只能'父传子',但不仅限'父传子'甚至'爷传孙'可以理解成
是一个加强版的'props'。
3.第三种'mixins' 混入继承,这种有点投机取巧适合接口,也就是提取出一个
公共的功能,在对应需要该接口的位置进行调用即可,这种不是传统意义父子
传值,更像是因为组件使用的数据来自一个接口,大家或者这个这接口数值
导致数据一致
4.利用自己的写的 'dispatch' 和 'broadcast' 进行传值,分别是'子孙像父组件'
'父组件像子孙传值',利用的原理是'$on' 和 '$emit'
~~~
>[info] ## 分析前辈们form 组件 -- iview
~~~
1.根据下面代码可以分析实际这个form组件是由两个组件构成的,分别是'Form'
和'FormItem' 组件构成的
2.'Form' 中有两个值分别是'model'和'rules' 依次表示是'对应的字段'和'表单验证规则'
3.'FormItem' 中的'prop' 对应着'model' 中的某个字段,'FormItem'中有个插槽可以填充,
各个输入框
~~~
~~~
<template>
<div>
<Form ref="formInline" :model="formInline" :rules="ruleInline" inline>
<FormItem prop="user">
<Input type="text" v-model="formInline.user" placeholder="Username">
<Icon type="ios-person-outline" slot="prepend"></Icon>
</Input>
</FormItem>
</Form>
</div>
</template>
<script>
export default {
name: 'home',
data(){
return{
formInline: {
user: '',
},
ruleInline: {
user: [
{ required: true, message: 'Please fill in the user name', trigger: 'blur' }
],
}
}
}
}
</script>
~~~
>[info] ## 开始定义自己form
~~~
1.根据上面分析,需要三个组件分别是'Form','Form-item','Input'
2.思路,定义验证规则传入给'Form','Form' 将规则分发给不同的'FormItem'
,每个'FormItem' 去进行验证
3.组件目录
│ ├── 'components' // 存放组件文件夹
│ │ └── 'Form' // 存放对iview 二次封装文件
│ │ └─ 'myFromItem.vue' // 每个单独输入框验证组件
│ │ └─ 'myFrom.vue' // 配置验证规则和字段
4.使用组件通信(三)中自定义封装的方法
~~~
>[danger] ##### 先自定义个一个input 输入框组件
~~~
1.常用v-model 绑定input,'v-model' 是一个语法糖,实际':value' 和'@input'
一个缩写,隐藏在写自己的input组件的时候需要拆开来定义
2.'input' 触发有两种一种是'input' 也就是输出就触发,'blur' 失去焦点触发,
因此组件触发要留出这两个触发事件,三还可以增加'change'事件
3.blur:失去焦点时触发,常见的有输入框失去焦点时触发校验;
change:实时输入时触发或选择时触发,常见的有输入框实时输入时触发校验、
下拉选择器选择项目时触发校验等。
~~~
~~~
<template>
<input
type="text"
:value="currentValue"
@blur="handleBlur"
@input="handleInput">
</template>
<script>
import Emitter from '../../mixins/emitter.js';
export default {
mixins:[Emitter],
props:{
// 接受父传子 过来的值,因为v-mode 是:value 和 @input的缩写
value:{
type:String,
default:''
}
},
data(){
return{
currentValue:this.value
}
},
methods:{
handleInput (event) {
// 通过event 获取当前值传递给父组件
const value = event.target.value;
this.currentValue = value;
this.$emit('input', value);
// 通过 自定义子传父 的方式 将自定义的触发名'on-form-change'传递给formItem
this.dispatch('iFormItem', 'on-form-change', value);
},
handleBlur () {
// 通过 自定义子传父 的方式 将自定义的触发名'on-form-change'传递给formItem
this.dispatch('iFormItem', 'on-form-blur', this.currentValue);
}
}
}
</script>
~~~
>[danger] ##### myFromItem.vue 设计
* 编写组件第一步从样式布局 和 生命周期要做的下手
~~~
1.html 的设计需要有label 和一个插槽,其中插槽用来和各种类型的input组
合,'当字段必填的时候' 需要给字段加一个红星
2.根据前辈的分装可以发现'FormItem' 主要填了'prop' 和 'label'也就是依次对
应在form 传入的规则字段,和label 需要展示的名字
3.分析生命周期要做的事,首先要知道父子组件的生命周期调用的顺序:
vm.created -》vm.beforedMount-》child.created-》child.beforeMount
-》child.mounted-》vm.mounted
根据上面分析简单的首先子组件的生命周期是最先执行完毕的简单的说,
最外层的父组件最开始 会调用自己的created 和beforedMount等生命周期
然后对应最内层的子组件会调用自己的完整的周期,然后再执行父组的'mounted'
生命周期
4.首先我们需要将子组件内容传递给父组件,也就是要将'FormItem' 中的内容传递给
'Form' 组件中,父传子的几种方式中我们选着我们自定义的写的封装方法传递,
让其直接触发的方式也有几种'生命周期','计算属性','watch监听',我们不需要吧所有
的'FormItem' 对象传递过去,也就是有'prop' 需要验证的传递给'Form' 组件 ,需要对
规则'Form' 中有'rules' 规则的字段加※号,关于触发'input' 传递过来的子传父调用
也应当在'mounted '声明周期,因为$on要想绑定监听就需要在生命周期中
4.1 总结上面 在'mounted' 生命周期要做的,将'FormItem' 对象子传父给'Form',
将'Form中的规则获取'并且如果是必填项将加上※,将获取input传递过来的触发事件
用$on 监听上,也就是'input' 和'blur' 事件中的方法
4.2 为了明确容易区分 分别定义了'getRules' 用来获取'Form' 父组件中的规则,和'setRules'
给'FormItem' 中必填项加※
5.当前生命周期结束后提供 给父组件一个效果 的方法
~~~
~~~
<template>
<div>
<label v-if="label" :class="{ 'i-form-item-label-required': isRequired }">{{label}}</label>
<slot></slot>
</div>
</template>
<script>
import Emitter from '../../mixins/emitter.js';
export default {
name: "mFormItem",
mixins:[Emitter],
inject:['form'],
props:{
label: {
type: String,
default: ''
},
prop:{
type:String,
},
},
data(){
return{
isRequired:false
}
},
methods:{
// 通过inject 接收form 中验证规则,获取对应字段验证条件
getRules(){
let formRules = this.form.rules
// 如果有就获取当前字段的规则
formRules = formRules ? formRules[this.prop] :[]
// concat 会生成新的数组返回
return [].concat(formRules || [])
},
// 获取规则操作给dom 加红色星星标记 和获取input 组件绑定的触发事件
setRules(){
let rules = this.getRules();
// 拿到 数组做是否有内容在做循环
if(rules.length){
// 循环每一项 在规则对象中看是否存在required 也就是必填
// 来看是否加星
rules.every((rule) => {
// 如果当前校验规则中有必填项,则标记出来
this.isRequired = rule.required;
});
}
// 将 父组件对象事件 单独提取出来写成方法
this.$on('on-form-blur', this.onFieldBlur);
this.$on('on-form-change', this.onFieldChange);
},
},
mounted(){
// 如果有prop 验证字段就将当期formItem 传递给Form
if(this.prop){
this.dispatch('mForm', 'on-form-item-add', this)
// 如果有调用 setRules 方法
this.setRules();
}
},
// 组件销毁前,将实例从 Form 的缓存中移除
beforeDestroy () {
this.dispatch('iForm', 'on-form-item-remove', this);
}
}
</script>
<style scoped>
.i-form-item-label-required:before {
content: '*';
color: red;
}
.i-form-item-message {
color: red;
}
</style>
~~~
* 第二步 从完善功能下手
~~~
1.上面已经从'Form' 获取当前'Form' 字段的验证,并对必填项做了处理,这
吧就需要获取当前验证的触发方式,如果有则继续执行
2.需要使用一个第三方的验证库,来专门验证一些表单规则'async-validator'
3.考虑重置功能,重置的数据也是form 的model 里面提供的因此,在item最开始,
生命周期初始的时候 就应该 保存要被重置的内容
~~~
* 最终完整代码
~~~
<template>
<div>
<label v-if="label" :class="{ 'i-form-item-label-required': isRequired }">{{label}}</label>
<slot></slot>
<div v-if="validateState === 'error'" class="i-form-item-message">{{ validateMessage }}</div>
</div>
</template>
<script>
import Emitter from '../../mixins/emitter.js';
import AsyncValidator from 'async-validator';
export default {
name: "mFormItem",
mixins:[Emitter],
inject:['form'],
props:{
label: {
type: String,
default: ''
},
prop:{
type:String,
},
},
data(){
return{
isRequired: false, // 是否为必填 加星星
validateState: '', // 校验状态
validateMessage: '', // 校验不通过时的提示信息
}
},
computed: {
// 从 Form 的 model 中动态得到当前表单组件的数据
fieldValue () {
return this.form.model[this.prop];
}
},
methods:{
// 通过inject 接收form 中验证规则,获取对应字段验证条件
getRules(){
let formRules = this.form.rules
// 如果有就获取当前字段的规则
formRules = formRules ? formRules[this.prop] :[]
// concat 会生成新的数组返回
return [].concat(formRules || [])
},
// 获取规则操作给dom 加红色星星标记 和获取input 组件绑定的触发事件
setRules(){
let rules = this.getRules();
// 拿到 数组做是否有内容在做循环
if(rules.length){
// 循环每一项 在规则对象中看是否存在required 也就是必填
// 来看是否加星
rules.every((rule) => {
// 如果当前校验规则中有必填项,则标记出来
this.isRequired = rule.required;
});
}
// 将 父组件对象事件 单独提取出来写成方法
this.$on('on-form-blur', this.onFieldBlur);
this.$on('on-form-change', this.onFieldChange);
},
// 获取'Form' 中 'rules' 触发验证规则是否是规定的 blur 和 change
getFilterRule(tigger){
const rules = this.getRules();
// 没填写验证规则 或者验证规则等于blur和change 的验证获取到
return rules.filter(rule => !rule.trigger || rule.trigger.indexOf(trigger) !== -1);
},
// 开始验证
validate(trigger, callback = function () {}){
let rules = this.getFilteredRule(trigger);
// 不符合 当前验证 则不往下执行
if (!rules || rules.length === 0) {
return true;
}
// 设置状态为校验中 如果是erro 则显示报错信息
this.validateState = 'validating';
// 使用第三方验证库来验证我们传入的规则
// 以下为 async-validator 库的调用方法
let descriptor = {};
descriptor[this.prop] = rules;
const validator = new AsyncValidator(descriptor);
let model = {};
// 在计算属性中获取 当前输入框的内容this.fieldValue
model[this.prop] = this.fieldValue;
validator.validate(model, { firstFields: true }, errors => {
this.validateState = !errors ? 'success' : 'error';
this.validateMessage = errors ? errors[0].message : '';
callback(this.validateMessage);
});
},
onFieldBlur() {
this.validate('blur');
},
onFieldChange() {
this.validate('change');
},
// 重置数据
resetField () {
this.validateState = '';
this.validateMessage = '';
this.form.model[this.prop] = this.initialValue;
},
},
mounted(){
// 如果有prop 验证字段就将当期formItem 传递给Form
if(this.prop) {
this.dispatch('mForm', 'on-form-item-add', this)
// 如果有调用 setRules 方法
this.setRules();
// 设置初始值,以便在重置时恢复默认值
this.initialValue = this.fieldValue;
}
},
// 组件销毁前,将实例从 Form 的缓存中移除
beforeDestroy () {
this.dispatch('iForm', 'on-form-item-remove', this);
}
}
</script>
<style scoped>
.i-form-item-label-required:before {
content: '*';
color: red;
}
.i-form-item-message {
color: red;
}
</style>
~~~
>[danger] ##### Form 组件分析
* 从生命周期开始下手
~~~
1.在'FormItem' 的生命周期中提供了传递给form组件的,当前对象的方法,
因此在'Form' 的生命周期开始的时候去接收,因为每个'FormItem' 是相互
独立的因此需要将他们统一保存起来,调用他们的重置和验证功能
2.我们刚才重置和验证都是给每个单独的'FormItme'做的,简单的说他们的
触发条件就是失去焦点和数据改变,因此有时候我们需要点击提交按钮
才触发判断每一个'FormItem' 是否好用,因此我们的'validate' 是验证所有的
3.代码的讲解
~~~
~~~
<template>
<form>
<slot></slot>
</form>
</template>
<script>
export default {
name: 'mForm',
props: {
model: {
type: Object
},
rules: {
type: Object
},
},
provide() {
return {
form : this
};
},
data () {
return {
fields: []
};
},
methods: {
// 公开方法:全部重置数据
resetFields() {
this.fields.forEach(field => {
field.resetField();
});
},
// 公开方法:全部校验数据,支持 Promise
validate(callback) {
return new Promise(resolve => {
let valid = true;
let count = 0;
this.fields.forEach(field => {
// 调用'Formitem' 中的验证方法,第二个参数是回调函数
// 验证的方法 如果没有target也就是触发判断的第一个参数为空或者是指定的'change'和'blur'都会触发
// 现在获取了每一个'FormItem' 中的验证是否正确并且调用回调可以触发我们自己规则
// 使用Promise 是为了支持异步
field.validate('', errors => {
if (errors) {
valid = false;
}
if (++count === this.fields.length) {
// 全部完成
resolve(valid);
if (typeof callback === 'function') {
callback(valid);
}
}
});
});
});
}
},
created () {
this.$on('on-form-item-add', (field) => {
if (field) this.fields.push(field);
});
this.$on('on-form-item-remove', (field) => {
if (field.prop) this.fields.splice(this.fields.indexOf(field), 1);
});
}
}
</script>
~~~
- Vue--基础篇章
- Vue -- 介绍
- Vue -- MVVM
- Vue -- 创建Vue实例
- Vue -- 模板语法
- Vue -- 指令用法
- v-cloak -- 遮盖
- v-bind -- 标签属性动态绑定
- v-on -- 绑定事件
- v-model -- 双向数据绑定
- v-for -- 只是循环没那么简单
- 小知识点 -- 计划内属性
- key -- 属性为什么要加
- 案例说明
- v-if/v-show -- 显示隐藏
- v-for 和 v-if 同时使用
- v-pre -- 不渲染大大胡语法
- v-once -- 只渲染一次
- Vue -- class和style绑定
- Vue -- filter 过滤器
- Vue--watch/computed/fun
- watch -- 巧妙利用watch思想
- Vue -- 自定义指令
- Vue -- $方法
- Vue--生命周期
- Vue -- 专属ajax
- Vue -- transition过渡动画
- 前面章节的案例
- 案例 -- 跑马灯效果
- 案例 -- 选项卡内容切换
- 案例-- 筛选商品
- 案例 -- 搜索/删除/更改
- 案例 -- 用computed做多选
- 案例 -- checked 多选
- Vue--组件篇章
- component -- 介绍
- component -- 使用全局组件
- component -- 使用局部组件
- component -- 组件深入
- component -- 组件传值父传子
- component -- 组件传值子传父
- component -- 子传父语法糖拆解
- component -- 父组件操作子组件
- component -- is 动态切换组件
- component -- 用v-if/v-show控制子组件
- component -- 组件切换的动画效果
- component -- slot 插槽
- component -- 插槽2.6
- component -- 组件的生命周期
- component -- 基础组件全局注册
- VueRouter--获取路由参数
- VueRouter -- 介绍路由
- VueRouter -- 安装
- VueRouter -- 使用
- VueRouter--router-link简单参数
- VueRouter--router-link样式问题
- VueRouter--router-view动画效果
- VueRouter -- 匹配优先级
- vueRouter -- 动态路由
- VueRouter -- 命名路由
- VueRouter -- 命名视图
- VueRouter--$router 获取函数
- VueRouter--$route获取参数
- VueRouter--路由嵌套
- VueRouter -- 导航守卫
- VueRouter -- 写在最后
- Vue--模块化方式结构
- webpack--自定义配置
- webpack -- 自定义Vue操作
- VueCli -- 3.0可视化配置
- VueCli -- 3.0 项目目录
- Vue -- 组件升级篇
- Vue -- 组件种类与组件组成
- Vue -- 组件prop、event、slot 技巧
- Vue -- 组件通信(一)
- Vue -- 组件通信(二)
- Vue -- 组件通信(三)
- Vue -- 组件通信(四)
- Vue -- 组件通信(五)
- Vue -- 组件通信(六)
- Vue -- bus非父子组件通信
- Vue -- 封装js插件成vue组件
- vue组件分装 -- 进阶篇
- Vue -- 组件封装splitpane(分割面板)
- UI -- 正式封装
- Vue -- iview 可编辑表格案例
- Ui -- iview 可以同时编辑多行
- Vue -- 了解递归组件
- UI -- 正式使用递归菜单
- Vue -- iview Tree组件
- Vue -- 利用通信仿写一个form验证
- Vue -- 使用自己的Form
- Vue -- Checkbox 组件
- Vue -- CheckboxGroup.vue
- Vue -- Alert 组件
- Vue -- 手动挂载组件
- Vue -- Alert开始封装
- Vue -- 动态表单组件
- Vue -- Vuex组件的状态管理
- Vuex -- 参数使用理解
- Vuex -- state扩展
- Vuex -- getters扩展
- Vuex--mutations扩展
- Vuex -- Action 异步
- Vuex -- plugins插件
- Vuex -- v-model写法
- Vuex -- 更多
- VueCli -- 技巧总结篇
- CLI -- 路由基础
- CLI -- 路由升级篇
- CLI --异步axios
- axios -- 封装axios
- CLI -- 登录写法
- CLI -- 权限
- CLI -- 简单权限
- CLI -- 动态路由加载
- CLI -- 数据性能优化
- ES6 -- 类的概念
- ES6类 -- 基础
- ES6 -- 继承
- ES6 -- 工作实战用类数据管理
- JS -- 适配器模式
- ES7 -- 装饰器(Decorator)
- 装饰器 -- 装饰器修饰类
- 装饰器--修饰类方法(知识扩展)
- 装饰器 -- 装饰器修饰类中的方法
- 装饰器 -- 执行顺序
- Reflect -- es6 自带版本
- Reflect -- reflect-metadata 版本
- 实战 -- 验证篇章(基础)
- 验证篇章 -- 搭建和目录
- 验证篇章 -- 创建基本模板
- 验证篇章 -- 使用
- 实战 -- 更新模型(为了迎合ui升级)
- 实战 -- 模型与接口对接
- TypeSprict -- 基础篇章
- TS-- 搭建(一)webpack版本
- TS -- 搭建(二)直接使用
- TS -- 基础类型
- TS -- 枚举类型
- TS -- Symbol
- TS -- interface 接口
- TS -- 函数
- TS -- 泛型
- TS -- 类
- TS -- 类型推论和兼容
- TS -- 高级类型(一)
- TS -- 高级类型(二)
- TS -- 关于模块解析
- TS -- 声明合并
- TS -- 混入
- Vue -- TS项目模拟
- TS -- vue和以前代码对比
- TS -- vue简单案例上手
- Vue -- 简单弄懂VueRouter过程
- VueRouter -- 实现简单Router
- Vue-- 原理2.x源码简单理解
- 了解 -- 简单的响应式工作原理
- 准备工作 -- 了解发布订阅和观察者模式
- 了解 -- 响应式工作原理(一)
- 了解 -- 响应式工作原理(二)
- 手写 -- 简单的vue数据响应(一)
- 手写 -- 简单的vue数据响应(二)
- 模板引擎可以做的
- 了解 -- 虚拟DOM
- 虚拟dom -- 使用Snabbdom
- 阅读 -- Snabbdom
- 分析snabbdom源码 -- h函数
- 分析snabbdom -- init 方法
- init 方法 -- patch方法分析(一)
- init 方法 -- patch方法分析(二)
- init方法 -- patch方法分析(三)
- 手写 -- 简单的虚拟dom渲染
- 函数表达解析 - h 和 create-element
- dom操作 -- patch.js
- Vue -- 完成一个minVue
- minVue -- 打包入口
- Vue -- new实例做了什么
- Vue -- $mount 模板编译阶段
- 模板编译 -- 分析入口
- 模板编译 -- 分析模板转译
- Vue -- mountComponent 挂载阶段
- 挂载阶段 -- vm._render()
- 挂载阶段 -- vnode
- 备份章节
- Vue -- Nuxt.js
- Vue3 -- 学习
- Vue3.x --基本功能快速预览
- Vue3.x -- createApp
- Vue3.x -- 生命周期
- Vue3.x -- 组件
- vue3.x -- 异步组件???
- vue3.x -- Teleport???
- vue3.x -- 动画章节 ??
- vue3.x -- 自定义指令 ???
- 深入响应性原理 ???
- vue3.x -- Option API VS Composition API
- Vue3.x -- 使用set up
- Vue3.x -- 响应性API
- 其他 Api 使用
- 计算属性和监听属性
- 生命周期
- 小的案例(一)
- 小的案例(二)-- 泛型
- Vue2.x => Vue3.x 导读
- v-for 中的 Ref 数组 -- 非兼容
- 异步组件
- attribute 强制行为 -- 非兼容
- $attrs 包括 class & style -- 非兼容
- $children -- 移除
- 自定义指令 -- 非兼容
- 自定义元素交互 -- 非兼容
- Data选项 -- 非兼容
- emits Option -- 新增
- 事件 API -- 非兼容
- 过滤器 -- 移除
- 片段 -- 新增
- 函数式组件 -- 非兼容
- 全局 API -- 非兼容
- 全局 API Treeshaking -- 非兼容
- 内联模板 Attribute -- 非兼容
- key attribute -- 非兼容
- 按键修饰符 -- 非兼容
- 移除 $listeners 和 v-on.native -- 非兼容
- 在 prop 的默认函数中访问 this -- ??
- 组件使用 v-model -- 非兼容
- 渲染函数 API -- ??
- Slot 统一 ??
- 过渡的 class 名更改 ???
- Transition Group 根元素 -- ??
- v-if 与 v-for 的优先级对比 -- 非兼容
- v-bind 合并行为 非兼容
- 监听数组 -- 非兼容