概述
社群vue基礎教程用到自定義的
v-validator
指令, 首次載入表單登入或註冊頁,都需要點選兩次才能正常跳轉
對該問題,本文提供了兩個解決辦法,一個是settimeout,另一個則利用js事件流原理,僅僅改變模板提交事件的繫結節點就可以了。
使用setTimeout
- 之前方案
vue最新版2.6.10,用登入或註冊方法,也隱式呼叫了驗證指令中繫結的點選事件處理邏輯,而nextTick
回撥函式塊內,有依賴該指令處理邏輯的程式碼canSubmit
。只要nextTick回撥函式體內的程式碼在指令事件邏輯呼叫之後執行,就不需要點選兩次了。例 如,用setTimeout
建個巨集任務(目的讓驗證指令內中的事件繫結邏輯先執行),程式碼如下
register(e) {
this.$nextTick(() => {
const target = e.target.type === "submit" ? e.target : e.target.parentElement;
setTimeout(() => {
if (target.canSubmit) {
this.submit();
}
});
});
}
分析
開始的時候也認為與
nexTick
有關,查閱了大量資料,發現vuejs 的nexttick
,與nodejs 中的nextick
理念一致的,都是在下一次巨集任務事件迴圈中,先處理掉當批次的微任務,再接著幹,沒毛病。vuejs
中的nextick
存在的意義在於,確保其回撥內的邏輯是在呼叫者對應的節點渲染完畢之後,再執行
雖然上述方案,也解決了兩次點選的問題,但始終不優雅。方案很難讓人信服,問題源自點選事件,理應從事件流的角度去解決,而非setTimeout。
事件繫結
自定義的 validator
指令,在v-validator
被繫結元素插入父節點時,會呼叫該指令的鉤子函式 inserted
。該鉤子內,會尋找表單內[type=submit]
元素節點,即提交按鈕 button
,並在該按鈕上動態繫結了監聽器
const submitBtn = form ? form.querySelector('[type=submit]') : null
if (submitBtn) {
const submitHandler = () => {
validate(el, modifiers, value)
const errors = form.querySelectorAll('.has-error')
if (!errors.length) {
submitBtn.canSubmit = true
} else {
submitBtn.canSubmit = false
}
}
submitBtn.addEventListener('click', submitHandler, false)
....
以登入元件,提交按鈕為例,顯然也繫結了一個click事件
<button @click="login" type="submit" class="btn btn-lg btn-success btn-block">
<i class="fa fa-btn fa-sign-in"></i> 登入
</button>
使用了
v-validator
自定義指令的表單,在執行驗證規則時,會隱式的在表單button提交按鈕上新增點選事件
該點選事件內,會在表單驗證不通過時,給button
節點物件新增一個canSubmit
標識 ,一個布值。
而該canSubmit
標識 值是模板上繫結的vue
點選@click=“login"
事件,決定能否提交的依據
同節點繫結多個相同事件
- 提交
button
元素節點上,同時註冊有兩個點選事件,一個隱式的指令事件,一個模板繫結的@click
事件 - 後者的處理邏輯依賴於前者,因此正常情況下,二者要有序,因此事件的執行順序決定了點選效果
- 對於dom2級事件addEventListener新增,其本身節點的事件處理邏輯由註冊順序所決定,即對同一節點元素上繫結的多個同類事件,觸發該節點事件,同類事件邏輯會依賴執行。示例程式碼如下
<button id="myBtn">點我</button>
<script>
var x = document.getElementById("myBtn");
x.addEventListener("click", myFunction);
x.addEventListener("click", someOtherFunction);
function myFunction() {
alert ("Hello World!")
}
function someOtherFunction() {
alert ("函式已執行!")
}
</script>
- 一次點選,依次彈出
Hello World! 函式已執行!
原理 事件流
捕獲 --> 目標事件 ---> 冒泡
- 既然順序,如此重要,改變模板同節點上的事件佈局,就可以實現事件有序執行。
推薦方案
- 僅僅只需要將模板提交按鈕上的點選事件繫結到提交按鈕的父節點上!!!
- 將模板上繫結的按鈕提交點選事件,繫結在
[type="submit"]
父節點上
這樣當點選登入時,產生點選事件,冒泡順序,會先觸發button按鈕上的點選事件,先呼叫執行按鈕上的隱式指令點選事件邏輯得到
canSubmit
值,後呼叫冒泡父節點span
上的click
事件,執行login
回撥。
<span @click="login">
<button type="submit" class="btn btn-lg btn-success btn-block">
<i class="fa fa-btn fa-sign-in"></i>登入
</button>
</span>
小結
- 對於vuejs元件模板節點繫結的事件,在當前節點的同類事件中,模板繫結回撥最先註冊到當前佇列
- 同樣的在編輯個人使用者資源時,首次進入該頁儲存也可以用同樣的方法解決