Vuejs - 深刻淺出響應式系統

Vue 最獨特的特性之一,是其非侵入性的響應式系統。數據模型僅僅是普通的 Javascript 對象。而當你修改它們時,視圖會進行更新。這使得狀態管理很是簡單直接,不過理解其工做原理一樣很是重要,這樣你能夠迴避一些常見的問題。html

原理圖剖析

vue-reactive-data

仔細閱讀這張官方原理圖,大概能夠剖析爲如下幾個步驟:vue

  • 編譯組件:對特殊標記的部分(好比雙大括號部分)進行替換爲相應的數據值。
  • 收集依賴:對於編譯階段依賴的數據進行監聽(這個都是經過 watcher 對象實現的)
  • 通知更新:當步驟2中監聽的數據發生變化時,會通知 watcher 進行從新計算,觸發關聯視圖更新。

能夠簡單理解爲一個發佈訂閱系統,固然這裏的過程介紹比較通俗,單純是爲了理解而解釋的,實際流程其實仍是很複雜的。若是想結合源碼深刻了解的,建議去閱讀這篇文章:
Vue 源碼解析:深刻響應式原理react

關於Vuejs模板

上一篇中提到,Vue實例在初始化的時候會將目標節點 div#app 內容進行替換,是在定義了 template 屬性或者 render 函數的前提下。不然會對把 div#app 內容當作模板進行編譯輸出。通常狀況下,不多使用 template 屬性來定義模板,由於實際開發過程當中,咱們的模板仍是很複雜的,單純經過 template 屬性定義,咱們的代碼會顯得很是臃腫,不便於閱讀。因此,咱們一般使用的是將模板內容寫在過載目標元素的內部,稍後將採用這種方法作代碼演示。git

固然對於複雜項目,更多的是使用 單文件組件 (這將在之後專題文章中介紹)。github

百聞不如一見

還記得上一篇中講述的如何實現一個簡單的 Vue 應用,並經過不一樣的 API 實現了目標元素的渲染吧。不記得也沒關係,這裏仍是從最基本的代碼開始。下面咱們會動態的實現一個列表的渲染,並經過修改數據,來觸發視圖的更新,以此來感覺下響應式系統的快感。先來看一段代碼:api

Demo1:data緩存

1
2
3
4
5
6
7
8
<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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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:computedapp

在實際開發過程當中,咱們的接口請求到的數據每每會差強人意,這時就須要咱們進行一些轉化,來生成咱們想要的數據結構,固然最直接的方式就是每次請求完數據就經過固定函數處理一遍,可是這個得手動執行。此時 computed 計算屬性就能夠用上了。ide

咱們知道除了 data 中定義的數據能夠再模板中使用外,computed 屬性也能夠。只不過 computed 中的屬性是須要先進行計算,而後再返回想要的數據的。當咱們輸出某個屬性,必須依賴另一個 data 中的屬性來動態計算得到的,此時使用 computed 就很是簡單了。代碼以下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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 屬性後,就能夠對它進行修改了。來看下面一段代碼:

1
2
3
4
<div id="app">
<button v-on:click="changeName">改變姓名</button>
<h2>{{ username }}</h2>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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 這個屬性,它實際上是個對象,鍵是須要觀察的表達式,值是對應的回調函數。值也能夠是方法名,或者包含選項的對象。和上面的計算屬性相似,他能夠監聽 值/表達式 的變化來執行回調函數。咱們先實現上面的功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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';
}
}
}
})

以上就是最基礎用法,每每有些時候咱們的監聽屬性並無那麼簡單。每每是一個對象,這時當咱們修改該對象的屬性時,如何實現監聽呢?先看下面代碼:

1
2
3
4
<div id="app">
<button v-on:click="changeName">改變姓名</button>
<h4>{{ username }}</h4>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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 來設置的。咱們再次修改代碼以下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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。再來看下面代碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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'
}
}
}
})

這樣咱們就能夠根據項目實際狀況,靈活的使用偵聽器來偵聽咱們所關注的屬性了,趕忙動手試一試吧~

源碼在此

相關文章
相關標籤/搜索