需求分析:
現在我們還需要一個整體的表單在單擊某個按鈕的時候可以循環的驗證每個input的值,最后我們還需要有一個事件可以得到最后驗證的結果,從而進行下一步的操作
如下,我們應該有一個form表單包裹著全部的input表單;然后有一個提交按鈕;點擊這個按鈕觸發一個事件去驗證包裹的這些input,從而獲取驗證的結果。
這里有一個難點就是獲取每一個input的驗證結果。
一、ValidateForm編碼 - 使用插槽slot(實現所有input都在form表單中)
如下我們創建好了基本結構后
用插槽實現相對應樣式
我們是在該組件內添加自定義的內容,那么我們想到之前Dropdown下拉菜單欄組件的slot插槽,當時下拉菜單的‘新建文字’‘編輯資料’‘退出登錄’我們是怎么做的呢。我們建一個DropdownItem組件是一個新建文字的一個選項的組件,在該組件中用插槽占位,Dropdown也是用插槽占位,然后我們在GlobalHearder標題組件組件中想要多少個下拉選項就直接在DropdownItem中插入即可,插入的這些DropdownItem們就是插入到Dropdown的。如下,這種就可以想要多少個選項就直接在GlobalHearder中加入即可,這樣不限定DropdownItem插入多少也不限定Dropdown插入多少,插入多少都可以
但是和之前這個稍微不同的呢,是我們的插槽其實分為2塊區域,一塊是這個input表單,一塊是提交按鈕的區域,不傳任何內容的時候它會渲染一個默認的按鈕,就等于其實我們有兩個可以自定義的插槽,我們去文檔看看這種方法應該怎么實現,如下通過添加name的方式
如下我們就填入了兩個具名插槽
我們到App.vue中導入感受一下
如下我們使用一個叫template的元素,template上有一個屬性叫v-slot,這個v-slot指令就是組件內部定義的這個模板名稱,它就等于這個submit,那個name為submit的插槽。也可以用縮寫:#submit 也是一樣的
然后我們做點擊提交按鈕發送事件。
在子組件中點擊按鈕,點擊事件發送后,在父組件中監聽結果。
首先我們要在emit字段里面確定我們要發送的這個自定義事件的名稱,之前我們都是使用Object定義它,其實如果沒有這個事件的驗證,也可以使用數組來定義要觸發的事件,我們事件這里就叫form-submit
那么這個事件什么時候觸發呢,我們可以給這個submit-area上面添加一個click事件叫submitForm,然后這個點擊事件中通過context.emit('form-submit',true)來觸發它
然后我們到父組件中監聽結果。我們創建一個函數來監聽結果,如下定義一個函數叫onFormSubmit,然后在這個子組件標簽中通過@form-submit="onFormSubmit"即可
如下,我們定義一個onFromSubmit的函數,子組件那個點擊事件中觸發的就是父組件中這個事件,這個事件中我們打印一下獲取的參數。
如下,點擊按鈕后,控制臺打印出傳過來的值了
二、ValidateForm編碼 - 嘗試父子通訊(在form組件中獲取input組件中驗證方法返回的值)
現在我們來做在form中完成所有input的驗證。
要想在父組件中訪問子組件的方法,那么我們就必須拿到這個方法,并且調用,那么怎樣拿到一個組件的實例呢,就是說你怎么在父組件App.vue中拿到子組件ValidateInput.vue中的實例(實例即有這個子組件的所有屬性、方法)
我們之前獲取dom節點,使用了ref這個屬性,即在這個dom節點中添加這個ref。那么當這個ref屬性不是添加到一個節點上,而是添加到一個自定義組件上又會發生什么有趣的事呢。
如下,我們在父組件App.vue的setup中定義一個ref響應式的變量叫inputRef,然后在子組件validate-input的標簽中加上ref="inputRef",然后通過inputRef.val就可以獲取這個子組件validate-input的實例,我們在點擊事件即onFormSubmit中打印出這個實例
如下可以看到,這個Proxy對象中的這些屬性和方法都是子組件validateinput組件的屬性和方法,即我們成功獲得了子組件的實例對象。我們是想得到驗證的input的結果,也就是我們想在父組件中獲得validateInput組件中驗證表單的方法validateInput返回的結果,結果是驗證為true還是false。這種方法我們可以獲取子組件實例,也就是我們就可以獲得子組件某方法返回的結果。
如上,子組件驗證規則這個方法中返回驗證結果,然后我們在父組件中通過inputRef.value.validateInput()即可獲取到子組件中這個驗證方法,如此父組件中即可拿到子組件中驗證方法返回的結果了
那這種做法可以在validateForm中使用嗎,也就是說我們想在validateForm組件中也獲取validateInput組件的驗證方法的返回值。
來看validateForm的結構,這時候發現這里沒有validate-input標簽,變成了slot標簽,而這個slot占位符可能有多個validate-input,我們沒法用一個變量或者數組來定義它即像上面那樣定義一個響應式變量,而且slot也不支持ref屬性,那么就無法像上面那樣通過定義一個響應式變量,然后在標簽中通過ref="這個響應式變量",然后通過這個響應式變量.value來獲得這個validateInput組件的實例。
那怎么辦呢,那么這個通過ref獲取另一個組件實例的方法走不通了,我們就需要其他的一種父組件和子組件通訊方式。
由于slot的特殊性,這時候我們就需要事件監聽器來幫忙了。
我們在父組件validate-form中創建一個事件監聽器,去監聽相應的事件;然后在子組件validate-input中通過某種方法往這個監聽器里面手動觸發事件,把想要的內容傳遞過去
我們來想想應該怎么實現
首先我們應該在父組件即validateForm中創建事件監聽,也就是this.$on(事件名稱A,傳過來的函數)創建一個事件監聽,然后創建一個數組為空,該函數就把子組件傳過來的函數一個個放到數組中;
然后在子組件即validateItem中我們怎么拿到父組件這個事件呢,其實有一個神奇的屬性稱為$parent,這個$parent可以用來從一個子組件直接訪問父組件的實例,所以我們在子組件中通過this.$parent.emit('A',validateInput驗證函數),就可以實現在子組件中獲取父組件的事件監聽函數,同時把子組件的驗證函數發過去。
假如email這個item被初始化的時候,email的validateChange函數就會被加到數組中,password這個item被初始化的時候,password的validateChange函數就會被加到數組中,最后在父組件中循環調用這個方法就可以看到每個子組件的執行結果了
接下來我們編碼
由于this在setup中無法訪問,所以我們先用這個vue2方法創建,如下,發現說vue3中關于這個事件的這三個$on、$off、$once已經被放棄,推薦mitt
三、ValidateForm編碼 - 尋找外援mitt
可以看到mitt是一個流行的庫
如下可以看到它的API有如下
首先我們來安裝它 npm install --save mitt,然后在validate-form組件中引入這個mitt
然后我們到validate-form組件中創建一個事件監聽器,并且監聽相應的事件。
我們在它用法中可以看到我們直接調用mitt() 函數就可以創建監聽器了
所以如下創建監聽器mitt(),因為我們要把這個監聽器給validate使用,所以我們要把它導出去
然后現在是有了監聽器,然后我們定義監聽事件callback,然后我們通過emitter.on()把這個監聽事件添加到監聽器中,注意在組件銷毀階段記得把創建的監聽器銷毀
現在這個監聽器已經設置完畢,它像一個收音機一樣正在等待接收信號,現在讓我們到validateInput組件中向它發動信息
首先我們要把監聽器導入進validateInput組件中,然后在組件onmounted后就可以把信息發送出去了。
如下,我們在onmounted中向監聽器中發送東西了
如下,validateInput那邊onmounted中把inputRef.val值傳給名叫form-item-created的監聽器中了,監聽器監聽到有信息來了,就去觸發綁在它身上的這個callback函數并且把傳過來的inputRef.val值這個值傳給這個callback,該函數中打印出傳過來的值
所以如圖,控制臺中就打印出了validateInput中傳過來的輸入框的值
這就說明了我們在form中設立的電臺成功的收到了input的信號,那我們就成功在兩個組件中打通了溝通的橋梁,當然通過這種方法可以發送各種內容,而不僅僅是這個val字符串。
這樣我們就實現了子組件向父組件傳東西的想法,這樣下面子組件把驗證函數或者說驗證結果傳給父組件就行得通了
四、ValidateForm - 傳遞子組件的驗證函數給父組件
子組件向父組件發送子組件中真實的驗證函數。
所以validateInput組件中這里應該傳入validateInput函數
然后validateForm中應該接,首先我們定義一個空數組,用來放置子組件validateInput傳過來的驗證函數,這些函數執行以后可以顯示錯誤的信息并且返回input是否通過驗證,所以我們要給它一個簡單的定義,如下,定義一個類型ValidateFunc是一個函數,返回的是布爾值
然后最重要的一步,就是在這個submitForm即提交事件中,循環執行funcArr數組中傳過來的這些驗證函數,并且返回所有結果的最終值,并且最后通過這個事件發送出去。
循環調用一系列方法并且最終返回一個布爾,當有一個返回false,那么說明里面有錯誤,那么整個表單的驗證就沒有通過,那么就想到了用every方法。
但是當你用every()方法去執行funcArr數組中傳過來的這些驗證函數時,會發現如果輸入框都為空,點擊提交,發現只執行了一個驗證函數,即只有一個input框顯示出不能為空的提示。
這是因為類似every()、some() 這些Array上面的方法,它會提前停止循環,當我們里面有一個驗證函數返回false時,所以最終結果就是false,所以它很聰明就不再執行后面的函數就直接返回false了節省代碼執行時間,而這不是我們想要的,因為后面的驗證函數不執行的話,就無法彈出后面輸入框‘不能為空’的提示
所以我們需要運行數組里面的所有函數,然后再去判斷是否通過。這時候我們就把every改為map,改成map后會生成一個運行函數以后最終生成一個布爾數組,所以map(func=>func)就生成了一個裝滿布爾值的數組,然后我們再使用every就可以解決,every就去判斷這些布爾值中有沒有false即可
如下,得到所有input組件中的驗證函數后都存放在一個數組中,然后遍歷執行這個數組中的所有函數,然后得到所有input是否全部通過驗證的結果即result,最后這個result被傳回到了父組件App.vue中
這樣就實現了,form組件中獲取全部input組件的驗證結果,然后判斷出整個是否通過驗證,并且整個form組件是否通過驗證的結果還傳回了App.vue組件中