by yugasun from yugasun.com/post/you-ma…
本文可全文轉載,但需要保留原作者和出處。
雖然說是Vuejs實踐,但是有些重要的理論還是必不可少的,本文將簡單的帶你瞭解 Vuejs的響應式原理
。
Vue 最獨特的特性之一,是其非侵入性的響應式系統。資料模型僅僅是普通的 Javascript 物件。而當你修改它們時,檢視會進行更新。這使得狀態管理非常簡單直接,不過理解其工作原理同樣非常重要,這樣你可以迴避一些常見的問題。
原理圖剖析
仔細閱讀這張官方原理圖,大概可以剖析為以下幾個步驟:
- 編譯元件:對特殊標記的部分(比如雙大括號部分)進行替換為相應的資料值。
- 收集依賴:對於編譯階段依賴的資料進行監聽(這個都是通過
watcher
物件實現的) - 通知更新:當步驟2中監聽的資料發生變化時,會通知
watcher
進行重新計算,觸發關聯檢視更新。
可以簡單理解為一個釋出訂閱系統,當然這裡的過程介紹比較通俗,單純是為了理解而解釋的,實際流程其實還是很複雜的。如果想結合原始碼深入瞭解的,建議去閱讀這篇文章:
Vue 原始碼解析:深入響應式原理
關於Vuejs模板
上一篇中提到,Vue例項在初始化的時候會將目標節點 div#app
內容進行替換,是在定義了 template
屬性或者 render
函式的前提下。否則會對把 div#app
內容當做模板進行編譯輸出。一般情況下,很少使用 template
屬性來定義模板,因為實際開發過程中,我們的模板還是很複雜的,單純通過 template
屬性定義,我們的程式碼會顯得非常臃腫,不便於閱讀。所以,我們通常使用的是將模板內容寫在過載目標元素的內部,稍後將採用這種方法做程式碼演示。
當然對於複雜專案,更多的是使用 單檔案元件 (這將在以後專題文章中介紹)。
百聞不如一見
還記得上一篇中講述的如何實現一個簡單的 Vue 應用,並通過不同的 API 實現了目標元素的渲染吧。不記得也不要緊,這裡還是從最基本的程式碼開始。下面我們會動態的實現一個列表的渲染,並通過修改資料,來觸發檢視的更新,以此來感受下響應式系統的快感。先來看一段程式碼:
Demo1:data
<div id="app">
<button v-on:click="addItem">新增</button>
<ul>
<li v-for="(item, index) in list" v-bind:key="index">
<a v-bind:href="item.url">{{ item.name }}</a>
</li>
</ul>
</div>
複製程式碼
var app = new Vue({
el: "#app",
data () {
return {
count: 1,
list: [
{
name: `Vuejs官網`,
url: `https://cn.vuejs.org`
},
{
name: `Github`,
url: `https://github.com`
},
{
name: `Yuga部落格1`,
url: `https://yugasun.com`
}
]
}
},
methods: {
addItem () {
this.count++
this.list.push({
name: `Yuga部落格` + this.count,
url: `https://yugasun.com`
})
}
}
})
複製程式碼
上面 Vue 例項在初始化的時候,屬性 data
中定義了一個站點列表,然後模板中通過 v-for
指令進行列表渲染,同時也使用了 v-bind 指令來進行屬性繫結,並且通過 v-on
指令來監聽按鈕的 click
事件來執行 addItem
方法。當點選新增按鈕,就會向 list
push一條資料,檢視會再次更新。(關於指令相關文章將在下一篇中講到,感興趣的同學可以先到官網學習瞭解。)
Demo2:computed
在實際開發過程中,我們的介面請求到的資料往往會差強人意,這時就需要我們進行一些轉化,來生成我們想要的資料結構,當然最直接的方式就是每次請求完資料就通過固定函式處理一遍,但是這個得手動執行。此時 computed
計算屬性就可以用上了。
我們知道除了 data
中定義的資料可以再模板中使用外,computed
屬性也可以。只不過 computed
中的屬性是需要先進行計算,然後再返回想要的資料的。當我們輸出某個屬性,必須依賴另外一個 data
中的屬性來動態計算獲得的,此時使用 computed
就非常簡單了。程式碼如下:
var app = new Vue({
el: "#app",
data () {
return {
count: 1,
// 實際開發中往往是通過介面請求獲取
requestList: [
`Vuejs官網-https://cn.vuejs.org`,
`Github-https://github.com`,
`Yuga部落格1-https://yugasun.com`
]
}
},
computed: {
list: function() {
var list = [];
this.requestList.map(function(item, index) {
var tempArr = item.split(`-`);
list.push({
name: tempArr[0],
url: tempArr[1]
});
})
return list;
}
},
methods: {
addItem () {
this.count++
this.requestList.push(`Yuga部落格` + this.count + `-https://yugasun.com`)
}
}
})
複製程式碼
關於計算屬性 – computed
關於計算屬性,其實有個細節是很多同學沒有注意到的,計算屬性實際上是可以修改的!那麼如何才能修改呢?
其實計算屬性不僅可以定義為一個函式,也可以定義為一個含有 get/set
屬性的物件。當我們定義為一個函式是,Vue 內部會預設將這個函式賦值給 get
屬性,一般 set
是未定義的。當我們定義 set
屬性後,就可以對它進行修改了。來看下面一段程式碼:
<div id="app">
<button v-on:click="changeName">改變姓名</button>
<h2>{{ username }}</h2>
</div>
複製程式碼
var app = new Vue({
el: "#app",
data () {
return {
firstName: `Yuga`,
lastName: `Sun`
}
},
computed: {
username: {
get: function() {
return this.firstName + ` ` + this.lastName;
},
set: function(newVal) {
var names = newVal.split(` `);
this.firstName = names[0];
this.lastName = names[1];
}
}
},
methods: {
changeName () {
if (this.username === `Yuga Sun`) {
this.username = `Summer Wu`;
} else {
this.username = `Yuga Sun`;
}
}
}
})
複製程式碼
當進行賦值操作 this.username = `Summer Wu`
時,計算屬性 username
的 set
函式就會被呼叫,同時也對 firstName
和 lastName
進行了相應的更新。這裡看似是直接進行賦值操作,其實也是通過間接修改 firstName
和 lastName
來實現 username
的更新的。因為 計算屬性是基於它依賴的值進行快取的
,如果它依賴的值沒有發生改變,它自己就沒法發生改變。
關於偵聽器 – watch
建立 Vue 應用時,我們還提到過 watch
這個屬性,它其實是個物件,鍵是需要觀察的表示式,值是對應的回撥函式。值也可以是方法名,或者包含選項的物件。和上面的計算屬性類似,他可以監聽 值/表示式
的變化來執行回撥函式。我們先實現上面的功能:
var app = new Vue({
el: "#app",
data () {
return {
firstName: `Yuga`,
lastName: `Sun`,
username: `Yuga Sun`
}
},
watch: {
firstName: function(val, oldVal) {
this.username = val + ` ` + this.lastName;
},
lastName: function (val, oldVal) {
this.username = this.firstName + ` ` + val;
}
},
methods: {
changeName () {
if (this.username === `Yuga Sun`) {
this.firstName = `Summer`;
this.lastName = `Wu`;
} else {
this.firstName = `Yuga`;
this.lastName = `Sun`;
}
}
}
})
複製程式碼
以上就是最基礎用法,往往有些時候我們的監聽屬性並沒有那麼簡單。往往是一個物件,這時當我們修改該物件的屬性時,如何實現監聽呢?先看下面程式碼:
<div id="app">
<button v-on:click="changeName">改變姓名</button>
<h4>{{ username }}</h4>
</div>
複製程式碼
var app = new Vue({
el: "#app",
data () {
return {
userinfo: {
firstName: `Yuga`,
lastName: `Sun`,
},
username: `Yuga Sun`
}
},
watch: {
userinfo: function (val, oldVal) {
this.username = val.firstName + ` ` + val.lastName;
}
},
methods: {
changeName () {
if (this.username === `Yuga Sun`) {
this.userinfo.firstName = `Summer`
this.userinfo.lastName = `Wu`
} else {
this.userinfo.firstName = `Yuga`
this.userinfo.lastName = `Sun`
}
}
}
})
複製程式碼
此時無論我們如何點選按鈕,都無法改變 username
的值,因為 watch
偵聽器預設只是偵聽該物件本身的賦值操作,也就是直接對 this.userinfo
進行賦值操作時的變化,並未對其內部屬性進行偵聽。實際上對於偵聽的值是可以為一個物件的,它還有個 deep
屬性,用來設定是否偵聽內部屬性的變化,而回撥函式是通過 handler
來設定的。我們再次修改程式碼如下:
var app = new Vue({
el: "#app",
data () {
return {
userinfo: {
firstName: `Yuga`,
lastName: `Sun`,
},
username: `Yuga Sun`
}
},
watch: {
userinfo: {
deep: true,
handler: function (val, oldVal) {
this.username = val.firstName + ` ` + val.lastName;
}
}
},
methods: {
changeName () {
if (this.username === `Yuga Sun`) {
this.userinfo.firstName = `Summer`
this.userinfo.lastName = `Wu`
} else {
this.userinfo.firstName = `Yuga`
this.userinfo.lastName = `Sun`
}
}
}
})
複製程式碼
點選按鈕,你會發現 username
可以根據按鈕點選更新了,這個屬性在實際專案中非常實用,因為往往我們修改資料時,並不是整體賦值,大部分時候都是區域性修改屬性的,所以這個時候就需要通過設定 deep
屬性為 true
,來達到我們的偵聽目的。
問題來了,當偵聽物件包含很多屬性,而我們只是需要監聽其中的一個或某幾個屬性,這時如果我們通過這種方式偵聽所有內部屬性的變化,自然就會造成記憶體的浪費。那麼能不能只偵聽單一內部屬性的變化呢?答案是肯定的。
其實在 watch
定義的時候,鍵是可以為需要觀察的表示式
的,表示式
就是說明,我們是可以寫成物件屬性訪問表示式的。比如我們只需要偵聽姓氏的修改,我們可以寫成 userinfo.lastName
。再來看下面程式碼:
var app = new Vue({
el: "#app",
data () {
return {
userinfo: {
firstName: `Yuga`,
lastName: `Sun`,
},
username: `Yuga Sun`
}
},
watch: {
`userinfo.lastName`: function (val, oldVal) {
this.username = this.userinfo.firstName + ` ` + val;
}
},
methods: {
changeName () {
if (this.username === `Yuga Sun`) {
this.userinfo.lastName = `Wu`
} else {
this.userinfo.lastName = `Sun`
}
}
}
})
複製程式碼
這樣我們就可以根據專案實際情況,靈活的使用偵聽器來偵聽我們所關注的屬性了,趕緊動手試一試吧~