這是我開始用 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 版元件。

Post a comment