南嶼 帶你 走進 vue
### Vue
> Vue是一個前端js框架,由尤雨溪開發,是個人專案
Vue近幾年來特別的受關注,三年前的時候angularJS霸佔前端JS框架市場很長時間,接著react框架橫空出世,因為它有一個特性是虛擬DOM,從效能上碾軋angularJS,這個時候,vue1.0悄悄的問世了,它的優雅,輕便也吸引了一部分使用者,開始收到關注,16年中旬,VUE2.0問世,這個時候vue不管從效能上,還是從成本上都隱隱超過了react,火的一塌糊塗,這個時候,angular開發團隊也開發了angular2.0版本,並且更名為angular,吸收了react、vue的優點,加上angular本身的特點,也吸引到很多使用者,目前已經迭代到5.0了。
學習vue是現在前端開發者必須的一個技能。
##### 前端js框架到底在幹嘛,為什麼要用
js框架幫助開發者寫js邏輯程式碼,在開發應用的時候js的功能劃分為如下幾點:
1. 渲染資料
2. 操作dom(寫一些效果)
3. 操作cookie等儲存機制api
在前端開發中,如何高效的操作dom、渲染資料是一個前端工程師需要考慮的問題,而且當資料量大,流向較亂的時候,如何正確使用資料,操作資料也是一個問題
而js框架對上述的幾個問題都有自己趨於完美的解決方案,開發成本降低。高效能高效率。唯一的缺點就是需要使用一定的成本來學習。
##### Vue官網介紹
vue是漸進式JavaScript框架
vue的主張較弱
“漸進式框架”和“自底向上增量開發的設計”是Vue開發的兩個概念
Vue可以在任意其他型別的專案中使用,使用成本較低,更靈活,主張較弱,在Vue的專案中也可以輕鬆融匯其他的技術來開發,並且因為Vue的生態系統特別龐大,可以找到基本所有型別的工具在vue專案中使用
特點:易用(使用成本低),靈活(生態系統完善,適用於任何規模的專案),高效(體積小,優化好,效能好)
Vue是一個MVVM的js框架,但是,Vue 的核心庫只關注檢視層,開發者關注的只是m-v的對映關係
### 與AngularJS的對比
Vue的很多api、特性都與angularJS相似,其實是因為Vue在開發的時候借鑑了很多AngularJS中的特點,而AngularJS中固有的缺點,在Vue中已經解決,也就是青出於藍而勝於藍,Vue的學習成本比AngularJS低很多,因為複雜性就低
AngularJS是強主張的,而Vue更靈活
Vue的資料流是單向的,資料流行更清晰
Angular裡指令可以是操作dom的,也可以封裝一段結構邏輯程式碼,例如:廣告展示模組
Vue中的指令只是操作dom的,用元件來分離結構邏輯
AngularJS的效能比不上Vue
### Vue的使用
Vue不支援IE8,因為使用了ES5的很多特性
可以直接通過script標籤來引入vue.js,有開發版本和生產版本,開發版本一般我們在開發專案的時候引入,當最後開發完成上線的時候引入生產版本,開發版本沒有壓縮的,並且有很多提示,而生產版本全部刪掉了
在Vue中提供了一個腳手架(命令列工具)可以幫我們快速的搭建基於webpack的開發環境...
#### Vue的例項
每一個應用都有一個根例項,在根例項裡我們通過元件巢狀來實現大型的應用
也就是說元件不一定是必須的,但是例項是必須要有的
在例項化例項的時候我們可以傳入一個;配置項,在配置項中設定很多屬性方法可以實現複雜的功能
在配置中可以設定el的屬性,el屬性代表的是此例項的作用範圍
在配置中同過設定data屬性來為例項繫結資料
### mvc/mvvm
[阮大神部落格](http://www.ruanyifeng.com/blog/2015/02/mvcmvp_mvvm.html)
mvc 分為三層,其實M層是資料模型層,它是真正的後端資料在前端js中的一個對映模型,他們的關係是:資料模型層和檢視層有對映關係,model改變,view展示也會更改,當view產生使用者操作或會反饋給controller,controller更改model,這個時候view又會進行新的資料渲染
![MVC](http://image.beekka.com/blog/2015/bg2015020105.png)
這是純純的MVC的模式,但是很多框架都會有一些更改
前端mvc框架,如angularjs,backbone:
![前端MVC](http://image.beekka.com/blog/2015/bg2015020108.png)
會發現,使用者可以直接操作controller(例如使用者更改hash值,conrtoller直接監聽hash值變化後執行邏輯程式碼,然後通知model更改)
控制器可以直接操作view,如果,讓某一個標籤獲得進入頁面獲得焦點,不需要model來控制,所以一般會直接操作(angularJS,指令)
view可以直接操作model (資料雙向繫結)
MVP:
[mvp](http://image.beekka.com/blog/2015/bg2015020109.png)
view和model不能直接通訊,所有的互動都由presenter來做,其他部分的通訊都是雙向的
view較薄 ,presenter較為厚重
MVVM:
[mvvm](http://image.beekka.com/blog/2015/bg2015020110.png)
MVVM和MVP及其相似,只是view和viewmodel的通訊是雙向繫結,view的操作會自動的像viewmodel通過
##### v-for
這是一個指令,只要有v-的就是指令(directive 操作dom )
在vue中可以通過v-for來迴圈資料的通知迴圈dom,語法是item in/of items,接收第二個引數是索引 (item,index) of items,還可以迴圈鍵值對,第一個引數是value,第二個是key,第三個依然是索引
##### v-on
在vue中還有v-on來為dom繫結事件,在v-on:後面加上要繫結的事件型別,值裡可以執行一些簡單javascript表示式:++ -- = ...
可以將一些方法設定在methods裡,這樣就可以在v-on:click的值裡直接寫方法名字可以,預設會在方法中傳入事件物件,當寫方法的時候加了()就可以傳參,這個時候如果需要事件物件,那就主動傳入$event
v-on繫結的事件可以是任意事件,v-on:可以縮寫為@
為什麼在 HTML 中監聽事件?
你可能注意到這種事件監聽的方式違背了關注點分離 (separation of concern) 這個長期以來的優良傳統。但不必擔心,因為所有的 Vue.js 事件處理方法和表示式都嚴格繫結在當前檢視的 ViewModel 上,它不會導致任何維護上的困難。實際上,使用 v-on 有幾個好處:
1. 掃一眼 HTML 模板便能輕鬆定位在 JavaScript 程式碼裡對應的方法。
2. 因為你無須在 JavaScript 裡手動繫結事件,你的 ViewModel 程式碼可以是非常純粹的邏輯,和 DOM 完全解耦,更易於測試。
3. 當一個 ViewModel 被銷燬時,所有的事件處理器都會自動被刪除。你無須擔心如何自己清理它們。
##### 模板語法
在vue中,我們使用mustache插值({{}})來將資料渲染在模板中
使用v-once指令可以控制只能插入一次值,當資料變化的時候,模板對應的檢視不更新
使用v-html指令可以解析html格式的資料
在html標籤屬性裡不能使用mustache插值,這個時候給元素新增動態屬性的時候使用v-bind來繫結屬性,可以縮寫成:
在使用v-bind繫結class和內聯樣式的時候,vue做了一些優化,可以使用物件語法和陣列的語法來控制
防止表示式閃爍:
1. v-cloak
給模板內的元素新增v-cloak屬性後,元素在vue沒有載入完的時候就有這個屬性,當vue載入完成後這個屬性就消失了,所以我們可以給這個屬性設定css樣式為隱藏
```
<style>
[v-cloak]{
visibility: hidden;
}
</style>
```
2. v-text/v-html
v-text會指定將模板內元素的textContent屬性替換為指令值所代表的資料,也可以用於防止閃爍
v-html可以解析標籤,更改元素的innerHTML,效能比v-text較差
3. v-pre
跳過元素和其子元素的編譯過程,可以用來顯示mustache
##### vue-resource
這是一款vue的外掛,可以用來進行資料互動,支援的請求方式:GET/POST/JSONP/OPTIONS...
這個外掛官方宣佈不在更新維護,也就是說盡量不要使用
##### 計算屬性、監聽
有的時候我們需要在模板中使用資料a,這個時候就需要用到表示式,但是有的地方我們需要對a資料進行一些簡單的處理後才能使用,那麼我們就會在表示式中寫一些js邏輯運算
```
<div id="example">
{{ message.split('').reverse().join('') }}
</div>
```
這樣我們的維護就會非常困難,也不便於閱讀
那め我們就可以在methods裡設定一個方法,在模板的表示式中使用這個方法
```
<p>Reversed message: "{{ reversedMessage() }}"</p>
// 在元件中
methods: {
reversedMessage: function () {
return this.message.split('').reverse().join('')
}
}
```
但是這個時候,只要vm中有資料變化,這個變化的資料可能和我們關注的資料無關,但是vm都會重新渲染模板,這個時候表示式中的方法就會重新執行,大大的影響效能
這個時候其實我們可以使用監聽器裡完成:
在vm例項中設定watch屬性,在裡面通過鍵值對來設定一些監聽,鍵名為資料名,值可以是一個函式,這個函式在資料改變之後才會執行,兩個引數分別是性格前的值和更改後的值
```
a: function (val, oldVal) {
console.log('new: %s, old: %s', val, oldVal)
}
```
值還可以是一個方法名字,當資料改變的時候這個方法會執行
當資料為object的時候,object的鍵值對改變不會被監聽到(陣列的push等方法可以),這個時候需要設定深度監聽:
```
c: {
deep:true,
handler:function (val, oldVal) {
console.log('new: %s, old: %s', val, oldVal)
}
},
```
監聽的handler函式前面的這幾種寫法都是在資料變化的時候才會執行,初始化的時候不會執行,但是如果設定immediate為true就可以了
```
num:{
immediate:true,
handler:function(val){
this.nums = val*2
}
}
```
我們在回到上面的問題,用監聽器加上immediate屬性就可以做到該效果,但是大家可以看到的是邏輯稍稍有點複雜
watch還可以通過例項物件直接使用:vm.$watch,返回一個取消監聽的函式,這個函式執行之後會取消監聽
我們一般都會用到一個叫計算屬性的東西來解決:
計算屬性就是在例項配置項中通過computed來為vm設定一個新的資料,而這個新資料會擁有一個依賴(一條已經存在的資料),當依賴傳送變化的時候,新資料也會發送變化
與方法的方式相比,它效能更高,計算屬性是基於它們的依賴進行快取的。計算屬性只有在它的相關依賴發生改變時才會重新求值。相比之下,每當觸發重新渲染時,呼叫方法將總會再次執行函式。
與watch相比,寫起來簡單,邏輯性更清晰,watch一般多用於,根據資料的變化而執行某些動作,而至於這些動作是在幹什麼其實無所謂,而計算屬性更有針對性,根據資料變化而更改另一個數據
計算屬性也擁有getter和setter,預設寫的是getter,設定setter執行可以當此計算屬性資料更改的時候去做其他的一些事情,相當於watch這個計算屬性
```
xm:{
get:function(){//getter 當依賴改變後設置值的時候
return this.xing+'丶'+this.ming
},
set:function(val){//setter 當自身改變後執行
this.xing = val.split('丶')[0]
this.ming = val.split('丶')[1]
}
}
```
##### 過濾器
vue中可以設定filter(過濾器)來實現資料格式化,雙花括號插值和 v-bind 表示式中使用
vue1.0的有預設的過濾器,但是在2.0的時候全部給去掉了
所以在vue中如果想要使用過濾器就需要自定義
自定義的方法有兩種:全域性定義和區域性定義,全域性定義的過濾器在任意的例項、元件中都可以使用,區域性定義就是在例項、元件中定義,只能在這個例項或元件中使用
1. 全域性定義
Vue.filter(name,handler)
name是過濾器的名字,handler是資料格式化處理函式,接收的第一個引數就是要處理的資料,返回什麼資料,格式化的結果就是什麼
在模板中通過 | (管道符) 來使用,在過濾器名字後面加()來傳參,引數會在handler函式中第二個及後面的形參來接收
```
<p>{{msg | firstUpper(3,2)}}</p>
Vue.filter('firstUpper',function (value,num=1,num2) {
console.log(num2)
return value.substr(0,num).toUpperCase()+value.substr(num).toLowerCase()
})
```
2. 區域性定義
在例項、元件的配置項中設定 filters,鍵名為過濾器名,值為handler
```
filters:{
firstUpper:function (value,num=1,num2) {
console.log(num2)
return value.substr(0,num).toUpperCase()+value.substr(num).toLowerCase()
}
}
```
注意:
過濾器只能在mustache插值、v-bind裡使用,其他的指令等地方都不能用
>作業:自定義類似於angularJS中的currency、order、filter過濾器
##### 條件渲染
在Vue中可以使用v-if來控制模板裡元素的顯示和隱藏,值為true就顯示,為false就隱藏
v-if控制的是是否渲染這個節點
當我們需要控制一組元素顯示隱藏的時候,可以用template標籤將其包裹,將指令設定在template上,等等vm渲染這一組元素的時候,不會渲染template
當有else分支邏輯的時候,可以給該元素加上v-else指令來控制,v-else會根據上面的那個v-if來控制,效果與v-if相反,注意,一定要緊挨著
還有v-else-if指令可以實現多分支邏輯
```
<input type="text" v-model="mode">
<template v-if="mode=='A'">
<h1>1.title</h1>
<p>我的第一個P標籤</p>
</template>
<template v-else-if="mode=='B'">
<h1>2.title</h1>
<p>我的第二個P標籤</p>
</template>
<template v-else-if="mode=='C'">
<h1>3.title</h1>
<p>我的第三個P標籤</p>
</template>
<template v-else>
<p>不好意思,輸入有誤</p>
</template>
```
需要注意的另一個地方是:Vue 會盡可能高效地渲染元素,通常會複用已有元素而不是從頭開始渲染。這樣確實能使Vue變得更快,效能更高,但是有的時候我們需要讓例項去更新dom而不是複用,就需要給dom加上不同的key屬性,因為vue在判斷到底渲染什麼的時候,包括哪些dom可以複用,都會參考key值,如果dom表現基本一致,符合複用的條件,但是key值不同,依然不會複用
Vue還提供了v-show指令,用法和v-if基本一樣,控制的是元素的css中display屬性,從而控制元素的顯示和隱藏 , 不能和v-else配合使用,且不能使用在template標籤上,因為template不會渲染,再更改它的css屬性也不會渲染,不會生效
###### v-if vs v-show
v-if 是“真正”的條件渲染,因為它會確保在切換過程中條件塊內的事件監聽器和子元件適當地被銷燬和重建。
v-if 也是惰性的:如果在初始渲染時條件為假,則什麼也不做——直到條件第一次變為真時,才會開始渲染條件塊。
相比之下,v-show 就簡單得多——不管初始條件是什麼,元素總是會被渲染,並且只是簡單地基於 CSS 進行切換。
一般來說,v-if 有更高的切換開銷,而 v-show 有更高的初始渲染開銷。因此,如果需要非常頻繁地切換,則使用 v-show 較好;如果在執行時條件很少改變,則使用 v-if 較好。
##### mixin
在Vue中,我們可以通過定義多個mixin來實現程式碼抽離複用,便於維護,提升頁面的邏輯性
要注意的是:data屬性不要使用mixin,因為從邏輯上來說,每一個例項、元件的資料都應該是獨立的
一個mixin其實就是一個純粹的物件,上面掛載著抽離出來的配置,在某一個例項中,通過mixins選項(陣列)匯入後,此例項就擁有匯入的mixin的配置
且匯入的配置不會覆蓋原有的,而是合併到一起
##### 虛擬dom
頻繁且複雜的dom操作通常是前端效能瓶頸的產生點,Vue提供了虛擬dom的解決辦法
虛擬的DOM的核心思想是:對複雜的文件DOM結構,提供一種方便的工具,進行最小化地DOM操作。這句話,也許過於抽象,卻基本概況了虛擬DOM的設計思想
(1) 提供一種方便的工具,使得開發效率得到保證
(2) 保證最小化的DOM操作,使得執行效率得到保證
也就是說,虛擬dom的框架/工具都是這麼做的:
1. 根據現有的真實dom來生成一個完整的虛擬dom樹結構
2. 當資料變化,或者說是頁面需要重新渲染的時候,會重新生成一個新的完整的虛擬dom
3. 拿新的虛擬dom來和舊的虛擬dom做對比(使用diff演算法),。得到需要更新的地方之後,更新內容
這樣的話,就能大量減少真實dom的操作,提高效能
##### 元件化
模組化就是將系統功能分離成獨立的功能部分的方法,一般指的是單個的某一種東西,例如js、css
而元件化針對的是頁面中的整個完整的功能模組劃分,元件是一個html、css、js、image等外鏈資源,這些部分組成的一個聚合體
優點:程式碼複用,便於維護
劃分元件的原則:複用率高的,獨立性強的
元件應該擁有的特性:可組合,可重用,可測試,可維護
##### 元件
在vue中,我們通過Vue.extend來建立Vue的子類,這個東西其實就是元件
也就是說Vue例項和元件的例項有差別但是差別不帶,因為畢竟一個是父類一個是子類
一般的應用,會擁有一個根例項,在根例項裡面都是一個一個的元件
因為元件是要嵌入到例項或者父元件裡的,也就是說,元件可以互相巢狀,而且,所有的元件最外層必須有一個根例項,所以元件分為:全域性元件和區域性元件
全域性元件在任意的例項、父級元件中都能使用,區域性元件只能在建立自己的父級元件或者例項中使用
元件通過不同的註冊方法成為全域性、區域性元件
建立元件:
```
Vue.extend(options)
```
全域性註冊:
```
var App = Vue.extend({
template:"<h1>hello world</h1>"
})
Vue.component('my-app',App)
```
簡便寫法:
```
// 建立元件構造器和註冊元件合併一起
Vue.component('hello',{//Vue會自動的將此物件給Vue.extend
template:"<h1>hello</h1>"
})
```
元件通過template來確定自己的模板,template裡的模板必須有根節點,標籤必須閉合
元件的屬性掛載通過:data方法來返回一個物件作為元件的屬性,這樣做的目的是為了每一個元件例項都擁有獨立的data屬性
區域性註冊:
```
new Vue({
el:"#app",
components:{
'my-app':App
}
})
```
簡便寫法:
```
data:{},
components:{
'hello':{
template:"<h1>asdasdasdasdasdas</h1>"
}
}
```
在例項或者元件中註冊另一個元件,這個時候,被註冊的元件只能在註冊它的例項或元件的模板中使用,一個元件可以被多個元件或例項註冊
###### 注意瀏覽器規則
因為vue在解析模板的時候會根據某些html的規則,例如,在table裡只能放tr,td,th..,如果放入元件不會解析 這個時候我們可以放入tr使用is方式來標識這個tr其實是元件
```
<table id="app">
<tr is="hello"></tr>
</table>
```
###### template
我們可以在html的某個地方通過template標籤來定義元件的模板,在元件的template屬性中通過選擇器指定對應的template標籤內容就可以了,注意,需要給template標籤加id來指定
```
<template id="my-hello">
<div>
<h1>hello world</h1>
<p>hahahah</p>
</div>
</template>
//元件中
template:"#my-hello"
```
###### is切換
在例項、元件的模板中的某一個標籤上,可以通過is屬性來指定為另一個目標的元件,這個時候我們一般會使用component標籤來佔位、設定is屬性來指定目標元件
```
<component :is="type"></component>
//元件中
data:{
type:'aaa'
},
components:{
'aaa':{template:"<h1>AAAAAAAAAAAAA</h1>"},
'bbb':{template:"<h1>BBBBBBBBBBBBB</h1>"}
}
```
###### 元件巢狀
應用中劃分的元件可能會很多,為了更好的實現程式碼複用,所以必然會存在元件的巢狀關係
元件設計初衷就是要配合使用的,最常見的就是形成父子元件的關係:元件 A 在它的模板中使用了元件 B。
###### prop 傳遞資料
元件例項的作用域是孤立的,父元件不能直接使用子元件的資料,子元件也不能直接使用父元件的資料
父元件在模板中使用子元件的時候可以給子元件傳遞資料
```
<bbb money="2"></bbb>
```
子元件需要通過props屬性來接收後才能使用
```
'bbb':{
props:['money']
```
如果父元件傳遞屬性給子元件的時候鍵名有'-',子元件接收的時候寫成小駝峰的模式
```
<bbb clothes-logo='amani' clothes-price="16.58"></bbb>
////
props:['clothesLogo','clothesPrice']
```
我們可以用 v-bind 來動態地將 prop 繫結到父元件的資料。每當父元件的資料變化時,該變化也會傳導給子元件
###### 單向資料流
Prop 是單向繫結的:當父元件的屬性變化時,將傳導給子元件,但是反過來不會。這是為了防止子元件無意間修改了父元件的狀態,來避免應用的資料流變得難以理解。
另外,每次父元件更新時,子元件的所有 prop 都會更新為最新值。這意味著你不應該在子元件內部改變 prop。如果你這麼做了,Vue 會在控制檯給出警告。
在兩種情況下,我們很容易忍不住想去修改 prop 中資料:
1. Prop 作為初始值傳入後,子元件想把它當作區域性資料來用;
2. Prop 作為原始資料傳入,由子元件處理成其它資料輸出。
對這兩種情況,正確的應對方式是:
定義一個區域性變數,並用 prop 的值初始化它:
```
props: ['initialCounter'],
data: function () {
return { counter: this.initialCounter }
}
//定義一個計算屬性,處理 prop 的值並返回:
props: ['size'],
computed: {
normalizedSize: function () {
return this.size.trim().toLowerCase()
}
}
```
>注意在 JavaScript 中物件和陣列是引用型別,指向同一個記憶體空間,如果 prop 是一個物件或陣列,在子元件內部改變它會影響父元件的狀態。
###### prop驗證
我們可以為元件的 prop 指定驗證規則。如果傳入的資料不符合要求,Vue 會發出警告。這對於開發給他人使用的元件非常有用
驗證主要分為:型別驗證、必傳驗證、預設值設定、自定義驗證
```
props:{
//型別驗證:
str:String,
strs:[String,Number],
//必傳驗證
num:{
type:Number,
required:true
},
//預設資料
bool:{
type:Boolean,
// default:true,
default:function(){
return true
}
},
//自定義驗證函式
nums:{
type:Number,
validator: function (value) {
return value %2 == 0
}
}
}
```
當父元件傳遞資料給子元件的時候,子元件不接收,這個資料就會掛載在子元件的模板的根節點上
##### slot
vue裡提供了一種將父元件的內容和子元件的模板整合的方法:內容分發,通過slot插槽來實現
1. 匿名插槽
```
<aaa>abc</aaa>
template:"<h1><slot></slot></h1>"
```
在父元件中使用子元件的時候,在子元件標籤內部寫的內容,在子元件的模板中可以通過<slot></slot>來使用
2. 具名插槽
父元件在子元件標籤內寫的多個內容我們可以給其設定slot屬性來命名,在子元件的模板通過通過使用帶有name屬性的slot標籤來放置對應的slot,當slot不存在的時候,slot標籤內寫的內容就出現
```
<my-button>提交</my-button>
<my-button>重置</my-button>
<my-button></my-button>
template:"<button><slot>按鈕</slot></button>"
```
##### transition
Vue提供了transition元件來幫助我們實現過渡效果,依據就是在控制元素顯示隱藏的時候為dom在指定的時刻新增上對應的類名
而我們只要在這些類名裡寫上對應的css樣式
在進入/離開的過渡中,會有 6 個 class 切換(v代表的是transition的name屬性的值)。
v-enter:定義進入過渡的開始狀態。在元素被插入時生效,在下一個幀移除。
v-enter-active:定義過渡的狀態。在元素整個過渡過程中作用,在元素被插入時生效,在 transition/animation 完成之後移除。這個類可以被用來定義過渡的過程時間,延遲和曲線函式。
v-enter-to: 2.1.8版及以上 定義進入過渡的結束狀態。在元素被插入一幀後生效 (於此同時 v-enter 被刪除),在 transition/animation 完成之後移除。
v-leave: 定義離開過渡的開始狀態。在離開過渡被觸發時生效,在下一個幀移除。
v-leave-active:定義過渡的狀態。在元素整個過渡過程中作用,在離開過渡被觸發後立即生效,在 transition/animation 完成之後移除。這個類可以被用來定義過渡的過程時間,延遲和曲線函式。
v-leave-to: 2.1.8版及以上 定義離開過渡的結束狀態。在離開過渡被觸發一幀後生效 (於此同時 v-leave 被刪除),在 transition/animation 完成之後移除。
![className](https://cn.vuejs.org/images/transition.png)
如果有多個元素需要用transition-group包裹,並且需要有key值做標記
animate.css:
引入animate.css之後,按照下面的寫法:
```
<transition
leave-active-class="animated fadeOut"
enter-active-class="animated slideInLeft">
<p v-if="isShow" class="box"></p>
</transition>
```
##### 渲染函式和jsx
在vue中我們可以不用template來指定元件的模板,而是用render函式來建立虛擬dom結構,用這種方法優點就是效能高,缺點就是使用成本高,程式碼可讀性較低,可以使用jsx來在render函式中建立,這樣既提高了效能,又減少了成本
但是,我們在使用了vue-cli腳手架之後,因為腳手架中有對template標籤轉換虛擬dom的處理,所以,不需要使用jsx,我們也能高效的轉換為createElement形式
##### Vue裡元件的通訊
通訊:傳參、控制(A操控B做一個事件)、資料共享
模式:父子元件間、非父子元件
1. 父元件可以將一條資料傳遞給子元件,這條資料可以是動態的,父元件的資料更改的時候,子元件接收的也會變化
子元件被動的接收父元件的資料,子元件不要再更改這條資料了
2. 父元件如果將一個引用型別的動態資料傳遞給子組價的時候,資料會變成雙向控制的,子元件改資料的時候父元件也能接收到資料變化,因為子元件改的時候不是在改資料(地址),而是在改資料裡的內容,也就是說引用型別資料的地址始終沒有變化,不算改父元件資料
父子間資料共享(雙向控制),基本不會使用,違背了單向資料流
3. 父元件可以將一個方法傳遞給子元件,子元件呼叫這個方法的時候,就可以給父元件傳遞資料
父元件被動的接收子元件的資料
4. 父元件可以將一個事件繫結在子元件的身上,這個事件的處理程式是父元件某一個方法,當子元件觸發自己的這個被繫結的事件的時候,相當於觸發了父元件的方法
父元件被動的接收子元件的資料
5. 在元件間可以用過ref形成ref鏈,元件還擁有一個關係鏈($parent,$children,$root),通過這兩種鏈;理論來說,任意的兩個元件都可以互相訪問,互相進行通訊
任意元件通訊,用的少...
6. event bus 事件匯流排 小天使 專注於非父子元件的通訊,其實父子元件也可以使用,只是沒有必要
在B元件的某個鉤子函式為event_bus繫結一個事件,事件的處理程式是B想做的事情
在A元件的某一個操作裡,觸發event_bus繫結的事件
7. 大量元件間資料共享的時候 vuex
##### 元件的生命週期
每一個元件或者例項都會經歷一個完整的生命週期,總共分為三個階段:初始化、執行中、銷燬
![生命週期圖示](https://cn.vuejs.org/images/lifecycle.png)
1. 例項、元件通過new Vue() 創建出來之後會初始化事件和生命週期,然後就會執行beforeCreate鉤子函式,這個時候,資料還沒有掛載ね,只是一個空殼,無法訪問到資料和真實的dom,一般不做操作
2. 掛載資料,繫結事件等等,然後執行created函式,這個時候已經可以使用到資料,也可以更改資料,在這裡更改資料不會觸發updated函式,在這裡可以在渲染前倒數第二次更改資料的機會,不會觸發其他的鉤子函式,一般可以在這裡做初始資料的獲取
3. 接下來開始找例項或者元件對應的模板,編譯模板為虛擬dom放入到render函式中準備渲染,然後執行beforeMount鉤子函式,在這個函式中虛擬dom已經建立完成,馬上就要渲染,在這裡也可以更改資料,不會觸發updated,在這裡可以在渲染前最後一次更改資料的機會,不會觸發其他的鉤子函式,一般可以在這裡做初始資料的獲取
4. 接下來開始render,渲染出真實dom,然後執行mounted鉤子函式,此時,元件已經出現在頁面中,資料、真實dom都已經處理好了,事件都已經掛載好了,可以在這裡操作真實dom等事情...
5. 當元件或例項的資料更改之後,會立即執行beforeUpdate,然後vue的虛擬dom機制會重新構建虛擬dom與上一次的虛擬dom樹利用diff演算法進行對比之後重新渲染,一般不做什麼事兒
6. 當更新完成後,執行updated,資料已經更改完成,dom也重新render完成,可以操作更新後的虛擬dom
7. 當經過某種途徑呼叫$destroy方法後,立即執行beforeDestroy,一般在這裡做一些善後工作,例如清除計時器、清除非指令繫結的事件等等
8. 元件的資料繫結、監聽...去掉後只剩下dom空殼,這個時候,執行destroyed,在這裡做善後工作也可以
##### vue-cli腳手架
現在使用前端工程化開發專案是主流的趨勢,也就是說,我們需要使用一些工具來搭建vue的開發環境,一般情況下我們使用webpack來搭建,在這裡我們直接使用vue官方提供的,基於webpack的腳手架工具:vue-cli
安裝方法:
```
# 全域性安裝 vue-cli
npm install --global vue-cli
# 建立一個基於 webpack 模板的新專案
vue init webpack my-project
//init之後可以定義模板的型別
# 安裝依賴,走你
cd my-project
npm install
npm run dev
```
模板型別:
simple 對應的是一個超級簡單的html檔案
webpack 在配置的時候可以選擇是否需要vue-router
注意的是,模板建立的時候會詢問使用需要使用ESLINT來標準化我們的程式碼
在腳手架中,開發目錄是src資料夾,build負責打包的,config是負責配置(內建伺服器的埠、proxy代理),static是靜態目錄,test是測試
src中main.js是入口檔案,在裡面建立了一個根例項,根例項的模板就是根元件App的模板,其他的元件都在根元件裡面進行巢狀實現。
每一個元件都是一個單檔案元件,這種檔案會被webpack利用vue-loader的工具進行編譯
template部分負責寫元件的模板內容,script中建立元件。style裡寫元件的樣式
assets目錄也是靜態目錄,在這個目標中的檔案我們使用相對路徑引入,而static目錄中的檔案使用絕對地址來引入
在style上新增scoped能使這個style裡的樣式只作用於當前的元件,不加scoped就是全域性樣式
習慣於在App.vue根元件的style裡寫全域性樣式,而每個元件的style最好都是區域性的
配置sass編譯環境
vue-cli沒有內建sass編譯,我們需要自己修改配置
1. 下載對應工具:node-sass(4.0.0) sass-loader
2. 在build目錄下的webpack.base.conf.js中的module.rule裡新增如下配置
```
{
test: /\.scss$/,
loader:'style-loader!css-loader!sass-loader'
}
```
3. 在需要使用scss程式碼的元件的style標籤中新增 lang='scss'
##### vue-router
現在的應用都流行SPA應用(single page application)
傳統的專案大多使用多頁面結構,需要切換內容的時候我們往往會進行單個html檔案的跳轉,這個時候受網路、效能影響,瀏覽器會出現不定時間的空白介面,使用者體驗不好
單頁面應用就是使用者通過某些操作更改位址列url之後,動態的進行不同模板內容的無重新整理切換,使用者體驗好。
Vue中會使用官方提供的vue-router外掛來使用單頁面,原理就是通過檢測位址列變化後將對應的路由元件進行切換(解除安裝和安裝)
###### 簡單路由實現:
1. 引入vue-router,如果是在腳手架中,引入VueRouter之後,需要通過Vue.use來註冊外掛
```
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
```
2. 建立router路由器
```
new Router(options)
```
3. 建立路由表並配置在路由器中
```
var routes = [
{path,component}//path為路徑,component為路徑對應的路由元件
]
new Router({
routes
})
```
4. 在根例項裡注入router,目的是為了讓所有的元件裡都能通過this.$router、this.$route來使用路由的相關功能api
```
new Vue({
el: '#app',
router,
template: '<App/>',
components: { App }
})
```
5. 利用router-view來指定路由切換的位置
6. 使用router-link來建立切換的工具,會渲染成a標籤,新增to屬性來設定要更改的path資訊,且會根據當前路由的變化為a標籤新增對應的router-link-active/router-link-exact-active(完全匹配成功)類名
```
<router-link to="main">main</router-link>
<router-link to="news">news</router-link>
.router-link-active{
color:red;
}
```
###### 多級路由:
在建立路由表的時候,可以為每一個路由物件建立children屬性,值為陣列,在這個裡面又可以配置一些路由物件來使用多級路由,注意:一級路由path前加'/'
```
const routes = [
{path:'/main',component:AppMain},
{path:'/news',component:AppNews,children:[
{path:'inside',component:AppNewsInside},
{path:'outside',component:AppNewsOutside}
]},
]
```
二級路由元件的切換位置依然由router-view來指定(指定在父級路由元件的模板中)
```
<router-link to='inside'>inside</router-link>
<router-link to='outside'>outside</router-link>
<router-view></router-view>
```
###### 預設路由和重定向:
當我們進入應用,預設像顯示某一個路由元件,或者當我們進入某一級路由元件的時候想預設顯示其某一個子路由元件,我們可以配置預設路由:
```
{path:'',component:Main}
```
當我們需要進入之後進行重定向到其他路由的時候,或者當url與路由表不匹配的時候:
```
{path:'',redirect:'/main'}
///...放在最下面
{path:'**',redirect:'/main'},
```
###### 命名路由
我們可以給路由物件配置name屬性,這樣的話,我們在跳轉的時候直接寫name:main就會快速的找到此name屬性對應的路由,不需要寫大量的urlpath路徑了
###### 動態路由匹配
有的時候我們需要在路由跳轉的時候跟上引數,路由傳參的引數主要有兩種:路徑引數、queryString引數
路由引數需要在路由表裡設定
```
{path:'/user/:id',component:User}
```
上面的程式碼就是給User路由配置接收id的引數,多個引數繼續在後面設定
在元件中可以通過this.$route.params來使用
queryString引數不需要在路由表設定接收,直接設定?後面的內容,在路由元件中通過this.$route.query接收
###### router-link
<router-link> 元件支援使用者在具有路由功能的應用中(點選)導航。 通過 to 屬性指定目標地址,預設渲染成帶有正確連結的 <a> 標籤,可以通過配置 tag 屬性生成別的標籤.。另外,當目標路由成功啟用時,連結元素自動設定一個表示啟用的 CSS 類名。
router-link的to屬性,預設寫的是path(路由的路徑),可以通過設定一個物件,來匹配更多
```
:to='{name:"detail",params:{id:_new.id},query:{content:_new.content}}'
```
name是要跳轉的路由的名字,也可以寫path來指定路徑,但是用path的時候就不能使用params傳參,params是傳路徑引數,query傳queryString引數
replace屬性可以控制router-link的跳轉不被記錄\
active-class屬性可以控制路徑切換的時候對應的router-link渲染的dom新增的類名
##### 程式設計式導航
有的時候需要在跳轉前進行一些動作,router-link直接跳轉,需要在方法裡使用$router的方法
router.push = router-link:to
router.replace = router-link:to.replace
router.go() = window.history.go
##### 路由模式
路由有兩種模式:hash、history,預設會使用hash模式,但是如果url裡不想出現醜陋hash值,在new VueRouter的時候配置mode值為history來改變路由模式,本質使用H5的histroy.pushState方法來更改url,不會引起重新整理,但是需要後端進行路由的配置
##### 路由鉤子
在某些情況下,當路由跳轉前或跳轉後、進入、離開某一個路由前、後,需要做某些操作,就可以使用路由鉤子來監聽路由的變化
全域性路由鉤子:
```
router.beforeEach((to, from, next) => {
//會在任意路由跳轉前執行,next一定要記著執行,不然路由不能跳轉了
console.log('beforeEach')
console.log(to,from)
//
next()
})
//
router.afterEach((to, from) => {
//會在任意路由跳轉後執行
console.log('afterEach')
})
```
單個路由鉤子:
只有beforeEnter,在進入前執行,to引數就是當前路由
```
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
```
路由元件鉤子:
```
beforeRouteEnter (to, from, next) {
// 在渲染該元件的對應路由被 confirm 前呼叫
// 不!能!獲取元件例項 `this`
// 因為當守衛執行前,元件例項還沒被建立
},
beforeRouteUpdate (to, from, next) {
// 在當前路由改變,但是該元件被複用時呼叫
// 舉例來說,對於一個帶有動態引數的路徑 /foo/:id,在 /foo/1 和 /foo/2 之間跳轉的時候,
// 由於會渲染同樣的 Foo 元件,因此元件例項會被複用。而這個鉤子就會在這個情況下被呼叫。
// 可以訪問元件例項 `this`
},
beforeRouteLeave (to, from, next) {
// 導航離開該元件的對應路由時呼叫
// 可以訪問元件例項 `this`
}
```
##### 命名檢視
有時候想同時(同級)展示多個檢視,而不是巢狀展示,例如建立一個佈局,有 sidebar(側導航) 和 main(主內容) 兩個檢視,這個時候命名檢視就派上用場了。你可以在介面中擁有多個單獨命名的檢視,而不是隻有一個單獨的出口。如果 router-view 沒有設定名字,那麼預設為 default。
```
<router-view class="view one"></router-view>
<router-view class="view two" name="a"></router-view>
<router-view class="view three" name="b"></router-view>
```
一個檢視使用一個元件渲染,因此對於同個路由,多個檢視就需要多個元件。確保正確使用 components 配置(帶上 s):
```
const router = new VueRouter({
routes: [
{
path: '/',
components: {
default: Foo,//預設的,沒有name的router-view
a: Bar,
b: Baz
}
}
]
})
```
##### prop將路由與元件解耦
在元件中接收路由引數需要this.$route.params.id,程式碼冗餘,現在可以在路由表裡配置props:true
```
{path:'detail/:id',component:AppNewsDetail,name:'detail',props:true}
```
在路由自己中可以通過props接收id引數去使用了
props:['id']
##### Axios 資料互動工具
vue官方宣佈在2.0版本中不再對Vue-resource進行維護了,推薦使用axios工具
注意,axios預設配置不會設定session-cookie,需要進行配置
axios.defaults.withCredentials = true
詳細請看[文件](https://www.kancloud.cn/yunye/axios/234845)
##### 響應式原理
因為vue是mvvm的框架,所以當資料變化的時候,檢視會立即更新,檢視層產生操作後會自動通知vm來更改model,所以我們可以實現雙向資料繫結,而其中的原理就是例項會將設定的data逐個遍歷利用Object.defineProperty給資料生成getter和setter,當資料變化地方時候setter會監聽到並且通知對應的watcher工具進行邏輯運算會更新檢視
vuex借鑑了flux和redux的思想,但是flux和redux是獨立且完整的架構,vuex是耦合與vue框架的,所以使用成本要比flux、redux低
### 宣告式渲染
在vue中,我們可以先在vue例項中宣告資料,然後通過{{}}等方式渲染在dom中
### Vuex
Vuex是vue官方的一款狀態管理工具,什麼是狀態呢?我們在前端開發中有一個概念:資料驅動,頁面中任意的顯示不同,都應該有一條資料來控制,而這條資料又叫做state,狀態。
在vue中。元件間進行資料傳遞、通訊很頻繁,而父子元件和非父子元件的通訊功能也比較完善,但是,唯一困難的就是多元件間的資料共享,這個問題由vuex來處理
##### Vuex的使用:
1. 建立store:
```
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
//可以設定store管理的state/getter,mutations,actions
const store = new Vuex.Store({
})
```
2. 設定state
>state就是一個純物件,上面有一些狀態掛載,而且一個應用應該只有一個數據源:單一狀態樹、唯一資料來源
```
import state from './modules/state'
//可以設定store管理的state/getter,mutations,actions
const store = new Vuex.Store({
state
})
```
3. 在根例項裡配置store
>這樣,我們就可以在任意的元件中通過this.$store來使用關於store的api
```
import store from './store'
new Vue({
el: '#app',
router,
store,
template: '<App/>',
components: { App }
})
```
4. 在元件中使用state
因為在元件中可以通過this.$store來訪問store,所以我們也可以通過this.$store.state來使用state中管理的資料
```
data(){
return {
num:this.$store.state.num
}
},
```
但是我們發現,這樣使用的話,當state的資料更改的時候,vue元件並不會重新渲染,不會觸發元件的相關生命週期函式
也就是說,如果想要在元件中響應式的使用的時候,我們需要通過計算屬性(computed)來使用
```
computed:{
num(){
return this.$store.state.num
}
}
```
這樣的寫法很無趣,而且如果使用的狀態較多會產生冗餘的感覺,所以vuex提供了mapState輔助函式,幫助我們在元件中獲取並使用vuex的store中儲存的狀態
所以我們可以這樣寫:
```
computed:mapState(['num']),
```
但是如果元件中已經有了num這個資料了,而state中的資料名字也叫num就會照成衝突,這個時候我們可以在元件使用state的時候,給狀態起個別名:
```
computed:mapState({
// _num:'num',//鍵名為別名,值字串代表的是真正的狀態
_num(state){//方法名為別名,函式體裡還可以對真正的狀態做出一些處理
return state.num
}
}),
```
但是,有的時候我們在元件中還有自己的業務邏輯需要用到計算屬性:
```
computed:{
a(){
return num+1
},
...mapState({
// _num:'num',//鍵名為別名,值字串代表的是真正的狀態
_num(state){//方法名為別名,函式體裡還可以對真正的狀態做出一些處理
return state.num
}
}),
},
```
5. getters
有的時候,我們需要根據state中的某一個狀態派生出一個新的狀態,例如,我們state中有一個num,在某些元件中需要用到是num的二倍的一個狀態,我們就可以通過getters來建立
```
const getters = {
doublenum(state){
return state.num*2
}
}
```
建立了之後,在元件中通過this.$store.getters來獲取裡面的資料
當然vuex也提供了mapGetters輔助函式來幫助我們在元件中使用getters裡的狀態,且,使用的方法和mapState一模一樣
5. 使用mutations更改state
我們不能直接在元件中更改state:this.$store.state.num=2,而是需要使用mutations來更改,mutations也是一個純物件,裡面包含很多更改state 的方法,這些方法的形參接收到state,在函式體裡更改,這時,元件用到的資料也會更改,實現響應式。
但是我們也不能直接呼叫mutations 的方法,需要使用this.$store.commit來呼叫,第一個引數為呼叫的方法名,第二げ引數為傳遞引數
```
const mutations = {
increment(state){
state.num++
}
}
```
vuex提供了mapMutations方法來幫助我們在元件中呼叫mutations 的方法,使用方法和mapState、mapGetters一樣
6. 使用actions來處理非同步操作
Action 類似於 mutation,不同在於:
Action 提交的是 mutation,而不是直接變更狀態。
Action 可以包含任意非同步操作。
也就是說,如果有這樣的需求:在一個非同步處理之後,更改狀態,我們在元件中應該先呼叫actions,來進行非同步動作,然後由actions呼叫mutation來更改資料
```
const actions = {
[CHANGE_NUM]({commit}){
alert(1)
setTimeout(() => {
let num = Math.floor(Math.random()*10)
//呼叫mitations的方法
commit(CHANGE_NUM,num)
}, 1000);
}
}
``
如上,actions的方法中可以進行非同步的動作,且形參會接收store,從中取出commit方法用以呼叫mutations的方法
在元件中通過this.$store.dispatch方法呼叫actions的方法
當然也可以使用mapMutations來輔助使用
元件使用資料且通過非同步動作更改資料的一系列事情:
1.生成store,設定state
2.在根例項中注入store
3.元件通過計算屬性或者mapState來使用狀態
4.使用者產生操作,呼叫actions的方法,然後進行非同步動作
5.非同步動作之後,通過commit呼叫mutations的方法
6.mutations方法被呼叫後,更改state
7.state中的資料更新之後,計算屬性重新執行來更改在頁面中使用的狀態
8.元件狀態被更改...建立新的虛擬dom......
9.元件的模板更新之後重新渲染在dom中
vuex的使用:
目前市場上有兩種使用vuex的情況,
第一種:將需要共享、需要管理的狀態放入vuex中管理,也就是說在必要時使用
第二種:將所有的資料都交由vuex管理,由vuex來承擔更多的責任,元件變得更輕量級,檢視層更輕
---
##### 自定義指令
在實現回到頂部功能的時候,我們寫了一個backTop元件,接下來需要通過監聽window.scroll事件來控制這個元件顯示隱藏
因為可能會有其他的元件會用到這樣的邏輯,所以將此功能做成一個自定義指令:
根據滾動的距離控制一個數據為true還是為false(v-scroll-show)
問題:
唯一需要注意的是,在指令