Vue.js + jQuery, 生命週期問題

最近踩了一個小坑,事情是這樣的:有一堆卡片,被裝在一個卡片的群組裡(卡包?),卡片是一群由Vue根據data而動態生成的component,每個卡片使用的樣式是semantic ui的card,並控制其初始化,因此一般來說可能會這樣做:

卡包: Deck.vue的template:
<!-- Deck.vue -->
<div class="ui special cards">
    here are my cards:
    <card v-for="(card, index) in cards_list" :card="card" :key="index"></card>
</div>
卡片: Card.vue的template:
<!-- Card.vue -->
<div class="card">
    <div class="blurring dimmable image">
        <div class="ui inverted dimmer">
            <div class="content">
                <div class="center">
                    <div class="ui primary button">Click me</div>
                </div>
            </div>
        </div>
        <img :src="card.src">
    </div>
    <div class="content">
        <a class="header" v-text="card.header"></a>
    </div>
</div>

 

而根據semantic ui官網的指引,要讓上列卡片產生hover dimmer的效果,就必須執行初始化:

$('.special.cards .image').dimmer({
    on: 'hover'
});

 

由於這些卡片是根據vue裡的data而動態產生的,因此第一個想到的就是:那就在卡包的mounted裡,執行這段初始化的程式碼就好了啊:

<!-- Deck.vue -->
<script>
    export default {
        mounted() {
            $('.special.cards .image').dimmer({
                on: 'hover'
            });
        }
    }
</script>

打完收工,這麼簡單的東西也要寫一篇?如果真的是這樣那就太好了。

 

後來在重整頁面測試時偶而會發現dimmer竟然會壞掉,而且時好時壞沒有規律可言,第一個想法就是:八成是 電腦壞了 噴bug了,於是開始一連串的F5測試,結果顯示,平均每10次左右dimmer就會沒反應一次,也就是說,使用者瀏覽這頁面有約莫10%機率看到壞掉的東西,怎麼可能花生這種樹,難道是我搞錯了他的生命週期嗎?讓我來試試:

研究一下文件才發現mounted並不保證所有子元件也都render完成,要在整體render完成後做事,就要使用.$nextTick

<!-- Deck.vue -->
<script>
    export default {
        mounted() {
            console.log('Deck mounted');
            this.$nextTick(  function() {
                $('.special.cards .image').dimmer({
                    on: 'hover'
                });
            });
        }
    }
</script>

 

回到頁面測試後,一樣還是時好時壞,但是實在想不到哪裡弄錯,於是只好打開萬能的chrome dev tool,利用設break point的方式來底爸個,(當然這裡可以利用在不同lifecycle hook log出資料來簡單測試),搭配很好用的vue dev tool,終於發現:

原來是因為我的資料是透過AJAX取得,雖然說vue會動態綁定,根據新的render出畫面,但是jQuery並沒有這份天生神力,因此每次重整的render好像兩匹馬在賽跑(AJAX & jQuery initialize),大部分時間AJAX成功取回資料的速度比較快,因此jQuery可以順利取得並初始化頁面上的元件,但是偶而在AJAX跑得比較慢的時候,jQuery先執行了,由於這時畫面上沒東西,因此比較晚被加入的元件便沒有被jQuery操作(初始化),造成時好時壞的現象。

 

既然知道原因,改進的思路也就出來了,我想到的是大致上有這幾種做法:

 

1. 每張卡片管自己的初始化,也就是說,當一張卡片生出來的時候,“自己初始化自己”
<!-- Card.vue -->
<script>
    export default {
        mounted() {
            console.log('Card mounted');
            $(this.$el).find('.dimmable.image').dimmer({
                on: 'hover'
            });
        }
    }
</script>

 

2. 頁面資料更新時初始化,也就是卡片清單更新時初始化
<!-- Deck.vue -->
<script>
    export default {
        updated() {
            console.log('Deck updated');
            $('.special.cards .image').dimmer({
                on: 'hover'
            });
        }
    }
</script>

 

最後我使用第一種方式來解決,因為我比較喜歡讓卡片本身發生的事放到卡片元件裡,當然,這邊要補充一下,不同的情況會有不同的解法,如果今天資料更新頻繁,或是更新邏輯繁雜且跟父元件有所互動,那第二種可能會是比較好的選擇,因為第一種方式在每個卡片生出來時都會執行一次初始化。

 

最後筆記一下,Vue這種透過資料綁定view跟jQuery這種操作DOM的library在搭配上必須要注意彼此的生命週期,以及資料讀取的時間點,(或是根本不該這樣用,哈哈),造成時好時壞的現象。有個方法可以模擬不同網速下的狀況:利用chrome dev tool的Network可以模擬例如3G網路下的使用者體驗,或許就能將資料讀取延遲差異表現出來。