>[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 合并行為 非兼容
- 監聽數組 -- 非兼容