輕前端札記 - 整合 Vue 導致 jQuery 事件/插件失效
2 |
這是我開始用 Vue + jQuery 寫「輕前端」最常遇到的困擾,網頁原本用 jQuery 在 HTML 元素加掛事件或用 jQuery("...").pluginName() 掛上套件,在加入 Vue 之後失效。我知道這聽起來很模糊,用實例展示一下,大家就知道我在說什麼了。
假設我有一個無病呻吟的無聊 jQuery Plugin,在 <input type="text"> 加上 keyup 事件,輸入非數字時顯示灰底背景,若為零則呈現淺藍,正數淺綠,負數淺紅。
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<title>jQuery + Vue 展示 1</title>
<style>
html,
body {
height: 100%;
margin: 0;
font-size: 10pt;
font-family: 微軟正黑體, Arial, Helvetica, sans-serif;
}
.content {
padding: 6px;
}
input { margin: 3px; }
</style>
</head>
<body>
<div class="content">
<div>
<input type="text" placeholder="Name" />
</div>
<div>
<input type="text" class="score" placeholder="Score" />
</div>
<div>
<input type="button" value="Submit" />
</div>
</div>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script>
jQuery.fn.extend({
scoreColor: function () {
return this.each(function () {
var inp = $(this);
inp.keyup(function() {
var v = inp.val();
var c = "#ddd";
if (/^[+-]?\d+$/.exec(v))
c = parseInt(v) == 0 ? "#eef" :
v.indexOf('-') === 0 ? "#fee" : "#efe";
inp.css("background-color", c);
}).keyup();
});
}
});
$(".score").scoreColor();
</script>
</body>
</html>
接著我想新增「Name 或 Score 欄位沒填就停用 Submit 鈕不給按」的邏輯,這種需求,用 MVVM 寫最簡單明瞭,所以我用 < src="https://cdn.jsdelivr.net/npm/vue@2.6.12"> 加掛 Vue.js,為兩個 input 加上 v-model 雙向繫結,為 button 加上 v-bind:disable 依 input 輸入狀況動態停用:
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<title>jQuery + Vue 展示 2</title>
<style>
html,
body {
height: 100%;
margin: 0;
font-size: 10pt;
font-family: 微軟正黑體, Arial, Helvetica, sans-serif;
}
.content {
padding: 6px;
}
input { margin: 3px; }
</style>
</head>
<body>
<div class="content">
<div>
<input type="text" placeholder="Name" v-model="Name" />
</div>
<div>
<input type="text" class="score" placeholder="Score" v-model="Score" />
</div>
<div>
<input type="button" value="Submit" v-bind:disabled="!Name || Score === ''" />
</div>
</div>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.12"></script>
<script>
jQuery.fn.extend({
scoreColor: function () {
return this.each(function () {
var inp = $(this);
inp.keyup(function() {
var v = inp.val();
var c = "#ddd";
if (/^[+-]?\d+$/.exec(v))
c = parseInt(v) == 0 ? "#eef" :
v.indexOf('-') === 0 ? "#fee" : "#efe";
inp.css("background-color", c);
}).keyup();
});
}
});
$(".score").scoreColor();
console.log($._data($(".score")[0], "events")["keyup"])
var vm = new Vue({
el: '.content',
data: {
Name: "",
Score: ""
}
});
console.log($._data($(".score")[0], "events"))
</script>
</body>
</html>
實際操作一下,Submit 鈕動態停用有生效,但 Score 依內容變色的效果沒了,一直維持灰色背景。究其原因,我們用 $(".score").scoreColor(); 為 Score 文字欄位元素加上 keyup() 事件,歷經 new Vue({ el: ".content", ... }),Score 欄位看似沒變,但已是 Vue 重新產生的全新元素實體,原本掛載的事件沒有被複製過來。為證明這點,我在 new Vue() 前後使用 console.log($._data($(".score")[0], "events")["keyup"]) 檢查 Score 欄位所掛的事件,可以發現原本的 keyup 事件在 new Vue() 後被消失了:
針對持續存在的靜態 HTML 元素,將 jQuery 設定事件時機延到 new Vue() 之後可以克服這個問題:
var vm = new Vue({
el: '.content',
data: {
Name: "",
Score: ""
}
});
$(".score").scoreColor();
再來點變化,加上用 v-show 動態切換輸入欄位顯示也還 OK:
但如果是用 v-if 切換 HTML 元素是否顯示的話就不妙了,v-if 會重新產生元素個體,只要切換過一次,new Vue() 之後才掛上的事件還是會消失。
除了 v-if,v-for 也會重新產生元素個體造成相同的問題。故要徹底解決,可考慮自己用 Vue 重寫事件或找對映該 jQuery 插件的 Vue 版元件。還有一個簡便有效的解法是寫個簡單 Component 把 jQuery 插件包起來,相關技巧可參考開發 Vue.js 元件封裝 Kendo UI 日期選擇器。
A common issue when adding Vue.js to existing jQuery webpage, the events will be gone after Vue render the DOM.
Comments
# by Ike
把「$(".score").scoreColor();」放在 mounted 裡面好不好呢?
# by Jeffrey
to lke, 加在 mounted 事件跟移至 Vue() 後方效果相同但語意更精確,我認為更好,但仍無法處理 v-if 或 v-for 情境,徹底解法還是包成 Vue 元件,用 Vue 改寫或找對映的 Vue 版元件。