2017年5月9日

那些關於 Vue 的小細節 - Computed 中 getter 和 setter 觸發的時間點




文章撰寫時使用 vue@2.3.2
在 Vue 中 computed 是經常會使用到的屬性,因為在 Vue 中透過 computed 會 cache 住沒有改變的資料,因此正確且適當的使用 computed 將可以減少資料重新運算的次數,讓網頁的效能提升。
但是在使用的過程中,有時候會發現 computed 怎麼樣就是不被觸發,這當中有些細節是我們可以進一步瞭解的。

computed 的基本觀念

在 Vue 中,computed 的屬性可以被視為像是 data 一樣,可以讀取和設值,因此在 computed 中可以分成 getter(讀取) 和 setter(設值),在沒有寫 setter 的情況下,computed 預設只有 getter ,也就是只能讀取,不能改變設值。
雖然說 computed 內的屬性可以被視為像是 data 一樣,但在使用上,一般還是會讓 computed 類似唯讀的狀態,也就是去處理 `data` 資料,然後把它吐出來使用。
另外,在 getter 中,要記得搭配使用 return 來把值返回出來。
基本的寫法如下:
預設只有 getter 的 computed
    
new Vue({
    computed: {
        computedData: function () {
            return // ...
        }
    }
})
    
有 setter 和 getter 的 computed
    
new Vue({
    computed: {
        computedData: {
            get: function () {
                return // ...
            },
            set: function () {
                // ...
            }
        }
    }
})
    

程式範例連結

下面的部分包含許多程式說明,你可以開啟這份 Codepen ,然後開啟 console 視窗搭配閱讀。

一般情況下 getter 觸發的時間點

在一般的情況下,我們可以這樣使用 computed (只有 getter)來更新資料,你可以直接打開 Codepen 的第一部分,或者程式碼如下:
    
    
<!-- template -->
<div id="computed-basic" v-cloak="v-cloak"> 
    <h3 class="title-border">一般情況 computed getter 被觸發的時間點</h3>
    <form class="pure-form">
      <input type="text" v-model="firstName"/>
      <input type="text" v-model="lastName"/>
    </form>
    <p>{{fullName}}</p>
</div>

    
    
// js: computed-basic
new Vue({
    el: '#computed-basic',
    data: {
        firstName: 'PJ',
        lastName: 'Chen'
    },
    computed: {
        fullName () {
            console.log('computed getter')
            return this.firstName + ' ' + this.lastName
        }
    },
    updated () {
        console.log('updated')
    }
})
    
在這個情況下,我們只要輸入 input 的內容,改變了 this.firstNamethis.lastName 時,就會觸發 getter,也就是說,computed 的 getter 會觀察被寫在裡面的資料,一般來說,當被觀察的資料改變時,這個 getter 就會被觸發
聽起來非常合理,但是我們用 console.log() 看一下,分別看 computed getterupdated 的時間點,你會發現,當我們在 input 輸入資料,會觸發 computed,同時也會觸發這個 vm 的 updated
img

getter 的例外情況:資料變更但 getter 不會被觸發

剛剛我們提到當 getter 裡面被觀察的資料有變更時,就會觸發 computed,但這個說其實並不完全正確,有些時候畫面更新了,資料變更了,但其實不會觸發 computed 裡面的 getter。
如果我們把 template 中的 fullName 拿掉,換成 firstName 和 lastName 時(程式碼如下):
     
<p> firstName: {{ firstName }}, lastName: {{ lastName }} </p>
    
也就是當我們的 template 中沒有馬上用到這個 computed 的資料時(這裡的話就是指 fullName),那麼 Vue 不知道你要用到 fullName ,因此即使我們變更了 this.firstNamethis.lastName,依然不會觸發 getter 。
我們可以在 console 中看到,firstName 和 lastName 資料變更的情況下,只會一直得到 updated 而已,computed 中的 getter 並不會被觸發。
img

一般情況下 setter 觸發的時間點

接著,讓我們來看一下在一般的情況下, computedsetter 什麼時候會被觸發:

<!-- template -->
 <div id="computed-setter-basic" v-cloak="v-cloak"> 
    <h3 class="title-border">一般情況 computed setter 被觸發的時間點</h3>
    <form class="pure-form">
      <input type="text" v-model="fullName"/>
    </form>
    <p>firstName: {{ firstName }} <br/> lastName: {{ lastName }}</p>
 </div>



// js
// computed-setter-basic
new Vue({
    el: '#computed-setter-basic',
    data: {
        firstName: 'PJ',
        lastName: 'Chen'
    },
    computed: {
        fullName: {
            get () {
                console.log('computed getter')
                return this.firstName + ' ' + this.lastName
            },
            set (value) {
                console.log('computed setter')
                this.firstName = value.split(' ')[0]
                this.lastName = value.split(' ')[1]
            }
        }
    },
    updated () {
        console.log('updated')
    }
})

template 中,我們可以看到,我們的 input 是直接綁 v-model="fullName" ,因此他會直接去修改 fullName 的值,而當前 fullName 是 computed 中的一個屬性,我們說過 computed 中的屬性就和 data 類似可以取值(getter)和設值(setter),這時候因為我們要對 fullName 設值,自然就會對應到 fullName 裡面的 setter(如果沒有設定 setter 是無法對 fullName 設值的)。
簡單來說,當 computed 的屬性要被設值時,就會觸發 setter,從 console 中我們也可以看到,當我在 input 中輸入內容時,fullName 會改變,fullName 改變的情況會觸發 setter ,接著,因為我的 setter 中所做的事會變更到 getter 中所觀察的資料,這時候才又觸發 getter 執行,最後重新 updated 畫面。也就是從 setter -> getter -> updated 這樣的過程,如下圖所示:
img

觸發 setter 不必然會觸發 getter

在上面的例子中,我們會先觸發 setter , 接著觸發 getter,最後 updated 畫面。但是其實 getter 會被觸發是因為我們在 setter 中變更到了被 getter 所觀察的資料。也就是說,如果我們的 setter 在執行時,並不會觸發 getter 所觀察的資料的話,那麼 getter 就不會被觸發。
例如,當我把上面程式碼的 setter 中對於資料的變更註解掉時:

set (value) {
    console.log('computed setter')
    // this.firstName = value.split(' ')[0]
    // this.lastName = value.split(' ')[1]
}
那麼即時我們在 input 中輸入內容,都只會觸發 setter 而不會觸發 getter。換句話說,setter 和 getter 是獨立觸發的,兩個被觸發的時間點是不同的。
img

總結

在這篇文章中,我們進一步瞭解了 Vue computed 中的 gettersetter,有幾個重點可以整理一下
  1. getter 和 setter 彼次觸發的時間點是獨立的。 getter 在大部分的時候是當內部觀察的資料有改變時會被觸發;setter 則是當被觀察的物件本身有改變時會被觸發。
  2. getter 在畫面中沒有使用到被觀察的物件時,不會被觸發。
這篇的內容主要是根據自身的經驗和理解,如果有任何錯誤,都歡迎不吝告知,以避免錯誤的知識傳遞,謝謝!

5/10 更新,感謝網友@Aysh Su ( 聖涵 / 亞所 ) 修正觀念錯誤

0 意見:

張貼留言