之前在在一開始接觸框架的時候,不是很清楚響應式原理到底是什麼,一直會把它和
responsive
這個東西搞混,因為兩個在中文翻起來都是響應式,官網雖然有提及這個部分的說明,但小時候不懂事就給它忽略過去了。
最近在用 Vue
做一些東西的時候,才慢慢瞭解到
Reactivity
的意思和重要性,在使用
Vue
的過程中,我們會發現當我們修改
JavaScript
的資料時,畫面就會自動的更新產生變化,之所以能這麼方便操作,都是由於
Vue
背後的響應式系統(reactivity
system),可見 Reactivity 在 Vue
中有多重要!!
也因此,錯誤的使用可能會導致
Vue 的 data
不會有即時更新的效果。
在 Vue
當中,每個組件實例都有相對應的
watcher 實例物件
,它會紀錄組件渲染過程中所有被觸碰到的屬性,當這些屬性的
setter
被觸發時,就會通知
watcher,進而促使組件重新渲染。
然而,有些情況下你可能會發現即使已經為
JavaScript
中的資料重新設值時,畫面卻沒有重新渲染,這有可能是受限於
JavaScript
物件底層的一些限制,讓我們來看看什麼情況下會導致資料更新了,畫面卻沒有重新渲染:
物件部分:一開始沒有被註冊到的物件不會響應式更新
這是一開始在使用 Vue
或搭配 AJAX
取得資料時很容易疏忽的部分。假設我們現在要做一個計數器,分別記錄不同動物的數量,當我點擊按鈕的時候,該動物的數量就會增加,template
的部分像這樣:
<!-- template --> <button @click="addCount('dog')">addDogs</button> <button @click="addCount('cat')">addCats</button> <button @click="addCount('penguin')">addPenguin</button> <p>dog {{ counter.dog }}, cat {{ counter.cat }}, penguin {{ counter.penguin }}</p>
在一開始的時,我們在
data 的
counter
屬性中註冊了
counter.dog 和
counter.cat
這兩個屬性,但是並沒有把
penguin
給註冊進去,我們可能想說等在
created Hook
中再把資料送進去(通常因為是要透過
AJAX
取的資料內容),順便建立
penguin
屬性就好,因此這時候
JavaScript 的部分像這樣:
new Vue({ el: '#unregister-object', data: { // 一開始沒有註冊 penguin,只註冊了 dog 和 cat counter: { dog: 0, cat: 0 } }, methods: { addCount(name) { this.counter[name] ++ } }, created() { // 在 created 的時候才建立 penguin 順便設值為 0 this.counter.penguin = 0 }, updated() { // 讓我們可以知道組件有被更新 console.log('view updated') } })
可以點這裡操作範例
@ Codepen
這時候你會發現一個現象,從
console
中我們可以看到,當你按了
addDogs 或
addCats
時,這個組件會立即的更新。可是當你按
addPenguin
時,畫面卻毫無反應,也不會更新。你可能會以為資料沒有被設定進去,可是如果你又回去點addDogs
或
addCats
讓畫面更新時,你會發現其實
penguin
的資料有被設定進去,只是沒有產生響應式的變化重新渲染畫面:
所以碰到這樣的問題,我們可以怎麼做呢?
方法一:在 data 中要把所有需要響應式的資料設定進去
如果你希望你的資料能夠在變更時讓畫面重新渲染,達到響應式的效果(Reactive),那麼最簡單的方式是在一開始的
data
中就把資料設定進去,在這裡我們就只要把
penguin 加到 data
中就可以,像是這樣:
data: { counter: { dog: 0, cat: 0, penguin: 0 // 一開始就把要響應式變化的資料設定進來 } }
方法二:動態添加新的響應式屬性
除了上面的方式,我們也可以透過
vm.$set(object, key, value)
動態新增響應式的屬性,那麼我們只需要在原本的
created(){...} 中改成:
// 也可以使用 Vue.set(object, key, value) created() { // 在 created 的時候才建立 penguin 順便設值為 0 // 使用 $set 動態新增響應式組件 this.$set(this.counter, 'penguin', 0) }
如此當 penguin
被點擊時,一樣會更新組件,重新渲染頁面。
方法三:建立新的物件
另一種當我們要一次更改或新增較多的屬性時,可以透過
Object.assign()
的方法重新建立一個新的物件讓
Vue 去監控,因此,在 created
中可以寫成:
created () { // 透過建立新的物件來新增物件的屬性 this.counter = Object.assign({}, this.counter, { penguin: 0, dog: 5 }) }
陣列部分:利用陣列索引直接設值時
剛剛提到的主要是物件的部分,陣列同樣的也會在某些情況下沒有辦法產生響應式的變化,因此如果我們希望畫面會隨著資料能夠有響應式的變化時,應該特別留意和避免。
Template
如下,主要是列出一個動物清單,當我按下
Change Animal
時,它會根據我所輸入的內容以陣列索引的方式變更陣列中的資料內容。另外我們多寫了一個
forceUpdate
的按鈕,點下去之後可以強制更新
vue 組件。
<!-- template --> <div id="change-by-array-index" v-cloak="v-cloak"> <h3>利用索引值變更 index 0 的值</h3> <input type="text" v-model="animal"/> <button v-on:click="changeAnimal">Change Animal</button> <ul> <li v-for="item in animals" v-bind:key="item">{{ item }}</li> </ul> <button v-on:click="$forceUpdate()">forceUpdate</button> </div>
JS 的部分有
changeAnimal 這個 function
會把使用者填寫的資料以陣列索引的方式代入
animals[0] 。
new Vue({ el: '#change-by-array-index', data: { animal: '', animals: ['<YOURAnimal>', 'cat', 'penguin', 'bird', 'rabbit'] }, methods: { changeAnimal () { // 當利用陣列索引直接設置值時 this.animals[0] = this.animal } } })
可以點這裡操作範例
@ Codepen
這時候會碰到跟剛剛物件時一樣的問題,就是當我填完
input 內容,按下
changeAnimal
的按鍵時,畫面沒有任何反應,這是因為
以陣列索引值的方式修改資料內容時,Vue
無法監測到
,因此雖然資料已經代進去了,但是
Vue 不會更新。
和剛剛類似,你可以透過按下
forceUpdate
這個按鈕來讓 Vue
強制更新,強制更新後你就會發現,畫面更新成你剛剛填入的資料內容,表示其實剛剛是有把資料設定到
Vue 當中,只是 Vue
沒有監測到,因此沒有觸發組件去做
update。
方法一:使用 Vue 可觀察到的陣列方法
在 Vue
中包含一組可以觀察陣列的方法,而這些方法將能促使畫面重新渲染。這些方法包含:push()、pop()、shift()、unshift()、splice()、sort()、reverse()。
因此,回到剛剛的例子上,我們可以使用
arr.splice(startIndex, deleteCount, addItem)
這個方法來把陣列中的內容抽換掉,因此我們可以把
methods 改成:
methods: { changeAnimal () { // 利用 arr.splice(startIndex, deleteCount, addItem) this.animals.splice(0, 1, this.animal) } }
方法二:使用 vm.$set
和物件類似,我們也可以使用
vm.$set(array, index, value)
來達到響應式修改資料內容,我們可以把
methods 改成:
methods: { changeAnimal () { // 利用 vm.$set(array, index, value) 方法 this.$set(this.animals, 0, this.animal) } }
如此一樣可以達到響應式變換陣列的資料內容。
瞭解 Vue Reactivity 的原理
See the Pen [Demo] Vue Reactivity in Vanilla JavaScript by PJCHEN (@PJCHENder) on CodePen.
總結
當你對於 Vue
的響應式原理(Reactivity
System)有了更多的瞭解後,相信你可以少踩到一些莫名其妙或不必要的坑。
操作範例:
參考資料
- Reactivity in Depth @ Vue 官網
- List Rendering Caveat @ Vue 官網
0 意見:
張貼留言