前言
本文是閱讀vue文檔時(shí)的收獲和心得,與vue文檔大部分不符,閱讀vue文檔請移步Vue.js
創(chuàng)建一個(gè) Vue 實(shí)例
每個(gè) Vue 應(yīng)用都是通過用 Vue 函數(shù)創(chuàng)建一個(gè)新的 Vue 實(shí)例開始的:
var vm = new Vue({
// 選項(xiàng)
})
數(shù)據(jù)與方法
當(dāng)一個(gè) Vue 實(shí)例被創(chuàng)建時(shí),它將data對象中的所有的屬性加入到 Vue 的響應(yīng)式系統(tǒng)中。當(dāng)這些屬性的值發(fā)生改變時(shí),視圖將會產(chǎn)生“響應(yīng)”,即匹配更新為新的值。
// 我們的數(shù)據(jù)對象
var data = { a: 1 }
// 該對象被加入到一個(gè) Vue 實(shí)例中
var vm = new Vue({
data: data
})
// 獲得這個(gè)實(shí)例上的屬性
// 返回源數(shù)據(jù)中對應(yīng)的字段
vm.a == data.a // => true
// 設(shè)置屬性也會影響到原始數(shù)據(jù)
vm.a = 2
data.a // => 2
// ……反之亦然
data.a = 3
vm.a // => 3
當(dāng)這些數(shù)據(jù)改變時(shí),視圖會進(jìn)行重渲染。值得注意的是只有當(dāng)實(shí)例被創(chuàng)建時(shí)data中存在的屬性才是響應(yīng)式的。
相比于react需要自己調(diào)用setState,vue對數(shù)據(jù)響應(yīng)式自己封裝了一層。
這里唯一的例外是使用 Object.freeze(),這會阻止修改現(xiàn)有的屬性,也意味著響應(yīng)系統(tǒng)無法再追蹤變化。
var obj = {
foo: 'bar'
}
Object.freeze(obj)
new Vue({
el: '#app',
data: obj
})
除了數(shù)據(jù)屬性,Vue 實(shí)例還暴露了一些有用的實(shí)例屬性與方法。它們都有前綴 $,以便與用戶定義的屬性區(qū)分開來。例如:
var data = { a: 1 }
var vm = new Vue({
el: '#example',
data: data
})
vm.$data === data // => true
vm.$el === document.getElementById('example') // => true
// $watch 是一個(gè)實(shí)例方法
vm.$watch('a', function (newValue, oldValue) {
// 這個(gè)回調(diào)將在 `vm.a` 改變后調(diào)用
})
實(shí)例生命周期鉤子
每個(gè) Vue 實(shí)例在被創(chuàng)建時(shí)都要經(jīng)過一系列的初始化過程——例如,需要設(shè)置數(shù)據(jù)監(jiān)聽、編譯模板、將實(shí)例掛載到 DOM 并在數(shù)據(jù)變化時(shí)更新 DOM 等。
同時(shí)在這個(gè)過程中也會運(yùn)行一些叫做生命周期鉤子的函數(shù),這給了用戶在不同階段添加自己的代碼的機(jī)會。
比如created鉤子可以用來在一個(gè)實(shí)例被創(chuàng)建之后執(zhí)行代碼:
new Vue({
data: {
a: 1
},
created: function () {
// `this` 指向 vm 實(shí)例
console.log('a is: ' + this.a)
}
})
// => "a is: 1"
生命周期鉤子的 this 上下文指向調(diào)用它的 Vue 實(shí)例。
不要在選項(xiàng)屬性或回調(diào)上使用</font>箭頭函數(shù),比如
created: () => console.log(this.a)或vm.$watch('a', newValue => this.myMethod())。因?yàn)榧^函數(shù)并沒有this,this會作為變量一直向上級詞法作用域查找,直至找到為止,經(jīng)常導(dǎo)致Uncaught TypeError: Cannot read property of undefined或Uncaught TypeError: this.myMethod is not a function之類的錯(cuò)誤。
生命周期圖示

-
beforeCreate()
- 類型:
Function - 詳細(xì):
- 在實(shí)例初始化之后,數(shù)據(jù)觀測 (data observer) 和 event/watcher 事件配置之前被調(diào)用。
- 類型:
-
created()
- 類型:
Function - 詳細(xì):
- 在實(shí)例創(chuàng)建完成后被立即調(diào)用。在這一步,實(shí)例已完成以下的配置:數(shù)據(jù)觀測 (data observer),屬性和方法的運(yùn)算,watch/event 事件回調(diào)。然而,掛載階段還沒開始,
$el屬性目前不可見。 - vm.$el
類型:Element
只讀
詳細(xì):
?Vue 實(shí)例使用的根 DOM 元素。
- 在實(shí)例創(chuàng)建完成后被立即調(diào)用。在這一步,實(shí)例已完成以下的配置:數(shù)據(jù)觀測 (data observer),屬性和方法的運(yùn)算,watch/event 事件回調(diào)。然而,掛載階段還沒開始,
- 類型:
-
beforeMount()
- 類型:
Function - 詳細(xì):
在掛載開始之前被調(diào)用:相關(guān)的render函數(shù)首次被調(diào)用。
該鉤子在服務(wù)器端渲染期間不被調(diào)用。
- 類型:
-
mounted()
類型:
Function-
詳細(xì):
el被新創(chuàng)建的vm.$el替換,并掛載到實(shí)例上去之后調(diào)用該鉤子。如果 root 實(shí)例掛載了一個(gè)文檔內(nèi)元素,當(dāng)mounted被調(diào)用時(shí)vm.$el也在文檔內(nèi)。注意
mounted不會承諾所有的子組件也都一起被掛載。如果你希望等到整個(gè)視圖都渲染完畢,可以用vm.$nextTick替換掉mounted:mounted: function () { this.$nextTick(function () { // Code that will run only after the // entire view has been rendered }) }該鉤子在服務(wù)器端渲染期間不被調(diào)用。
-
beforeDestory()
- 類型:
Function - 詳細(xì):
實(shí)例銷毀之前調(diào)用。在這一步,實(shí)例仍然完全可用。
該鉤子在服務(wù)器端渲染期間不被調(diào)用。
- 類型:
-
destoryed()
- 類型:
Function - 詳細(xì):
Vue 實(shí)例銷毀后調(diào)用。調(diào)用后,Vue 實(shí)例指示的所有東西都會解綁定,所有的事件監(jiān)聽器會被移除,所有的子實(shí)例也會被銷毀。
該鉤子在服務(wù)器端渲染期間不被調(diào)用。
還有兩個(gè)函數(shù)updated()和beforeUpdate()在Mounted期間觸發(fā) - 類型:
-
beforeUpdate
類型:
Function-
詳細(xì):
數(shù)據(jù)更新時(shí)調(diào)用,發(fā)生在虛擬 DOM 打補(bǔ)丁之前。這里適合在更新之前訪問現(xiàn)有的 DOM,比如手動(dòng)移除已添加的事件監(jiān)聽器。該鉤子在服務(wù)器端渲染期間不被調(diào)用,因?yàn)橹挥谐醮武秩緯诜?wù)端進(jìn)行。
-
updated
類型:
Function-
詳細(xì):
由于數(shù)據(jù)更改導(dǎo)致的虛擬 DOM 重新渲染和打補(bǔ)丁,在這之后會調(diào)用該鉤子。
當(dāng)這個(gè)鉤子被調(diào)用時(shí),組件 DOM 已經(jīng)更新,所以你現(xiàn)在可以執(zhí)行依賴于 DOM 的操作。然而在大多數(shù)情況下,你應(yīng)該避免在此期間更改狀態(tài)。如果要相應(yīng)狀態(tài)改變,通常最好使用計(jì)算屬性或 watcher 取而代之。注意
updated不會承諾所有的子組件也都一起被重繪。如果你希望等到整個(gè)視圖都重繪完畢,可以用 vm.$nextTick 替換掉updated:updated: function () { this.$nextTick(function () { // Code that will run only after the // entire view has been re-rendered }) }該鉤子在服務(wù)器端渲染期間不被調(diào)用。
-
activated
類型:
Function-
詳細(xì):
keep-alive 組件激活時(shí)調(diào)用。
該鉤子在服務(wù)器端渲染期間不被調(diào)用。
-
deactivated
類型:
Function-
詳細(xì):
keep-alive 組件停用時(shí)調(diào)用。
該鉤子在服務(wù)器端渲染期間不被調(diào)用。
errorCaptured
2.5.0+ 新增
類型:(err: Error, vm: Component, info: string) => ?boolean
詳細(xì):
當(dāng)捕獲一個(gè)來自子孫組件的錯(cuò)誤時(shí)被調(diào)用。此鉤子會收到三個(gè)參數(shù):錯(cuò)誤對象、發(fā)生錯(cuò)誤的組件實(shí)例以及一個(gè)包含錯(cuò)誤來源信息的字符串。此鉤子可以返回 false 以阻止該錯(cuò)誤繼續(xù)向上傳播。
?
你可以在此鉤子中修改組件的狀態(tài)。因此在模板或渲染函數(shù)中設(shè)置其它內(nèi)容的短路條件非常重要,它可以防止當(dāng)一個(gè)錯(cuò)誤被捕獲時(shí)該組件進(jìn)入一個(gè)無限的渲染循環(huán)。
?
錯(cuò)誤傳播規(guī)則
?
默認(rèn)情況下,如果全局的 config.errorHandler 被定義,所有的錯(cuò)誤仍會發(fā)送它,因此這些錯(cuò)誤仍然會向單一的分析服務(wù)的地方進(jìn)行匯報(bào)。
?
如果一個(gè)組件的繼承或父級從屬鏈路中存在多個(gè) errorCaptured 鉤子,則它們將會被相同的錯(cuò)誤逐個(gè)喚起。
?
如果此 errorCaptured 鉤子自身拋出了一個(gè)錯(cuò)誤,則這個(gè)新錯(cuò)誤和原本被捕獲的錯(cuò)誤都會發(fā)送給全局的 config.errorHandler。
?
一個(gè) errorCaptured 鉤子能夠返回 false 以阻止錯(cuò)誤繼續(xù)向上傳播。本質(zhì)上是說“這個(gè)錯(cuò)誤已經(jīng)被搞定了且應(yīng)該被忽略”。它會阻止其它任何會被這個(gè)錯(cuò)誤喚起的 errorCaptured 鉤子和全局的 config.errorHandler。
模板語法
在底層的實(shí)現(xiàn)上,Vue 將模板編譯成虛擬 DOM 渲染函數(shù)。結(jié)合響應(yīng)系統(tǒng),Vue 能夠智能地計(jì)算出最少需要重新渲染多少組件,并把 DOM 操作次數(shù)減到最少。
數(shù)據(jù)綁定最常見的形式就是使用“Mustache”語法 (雙大括號) 的文本插值:
<span>Message: {{ msg }}</span>
通過使用 v-once 指令,你也能執(zhí)行一次性地插值,當(dāng)數(shù)據(jù)改變時(shí),插值處的內(nèi)容不會更新。但請留心這會影響到該節(jié)點(diǎn)上的其它數(shù)據(jù)綁定:
<span v-once>這個(gè)將不會改變: {{ msg }}</span>
雙大括號會將數(shù)據(jù)解釋為普通文本,而非 HTML 代碼。為了輸出真正的 HTML,你需要使用 v-html 指令:
<p>Using mustaches: {{ rawHtml }}</p>
<p>Using v-html directive: <span v-html="rawHtml"></span></p>

注意,你不能使用 v-html 來復(fù)合局部模板,因?yàn)?Vue 不是基于字符串的模板引擎。反之,對于用戶界面 (UI),組件更適合作為可重用和可組合的基本單位。
你的站點(diǎn)上動(dòng)態(tài)渲染的任意 HTML 可能會非常危險(xiǎn),因?yàn)樗苋菀讓?dǎo)致 XSS 攻擊。請只對可信內(nèi)容使用 HTML 插值,絕不要對用戶提供的內(nèi)容使用插值。
這個(gè)屬性類似于React的dangerouslySetInnerHTML
特性
Mustache 語法不能作用在 HTML 特性上,遇到這種情況應(yīng)該使用 v-bind 指令:
<div v-bind:id="dynamicId"></div>
使用 JavaScript 表達(dá)式
迄今為止,在我們的模板中,我們一直都只綁定簡單的屬性鍵值。但實(shí)際上,對于所有的數(shù)據(jù)綁定,Vue.js 都提供了完全的 JavaScript 表達(dá)式支持。
{{ number + 1 }}
{{ ok ? 'YES' : 'NO' }}
{{ message.split('').reverse().join('') }}
<div v-bind:id="'list-' + id"></div>
指令
指令 (Directives) 是帶有 v- 前綴的特殊特性。指令特性的值預(yù)期是單個(gè) JavaScript 表達(dá)式。指令的職責(zé)是,當(dāng)表達(dá)式的值改變時(shí),將其產(chǎn)生的連帶影響,響應(yīng)式地作用于 DOM。
v-on 指令,它用于監(jiān)聽 DOM 事件:
<a v-on:click="doSomething">...</a>
動(dòng)態(tài)參數(shù)
2.6.0 新增
<a v-bind:[attributeName]="url"> ... </a>
這里的 attributeName 會被作為一個(gè) JavaScript 表達(dá)式進(jìn)行動(dòng)態(tài)求值,求得的值將會作為最終的參數(shù)來使用。例如,如果你的 Vue 實(shí)例有一個(gè) data 屬性 attributeName,其值為 "href",那么這個(gè)綁定將等價(jià)于 v-bind:href
另外,如果你在 DOM 中使用模板 (直接在一個(gè) HTML 文件里撰寫模板),需要留意瀏覽器會把特性名全部強(qiáng)制轉(zhuǎn)為小寫:
<!-- 在 DOM 中使用模板時(shí)這段代碼會被轉(zhuǎn)換為 `v-bind:[someattr]` -->
<a v-bind:[someAttr]="value"> ... </a>
縮寫
Vue 為 v-bind 和 v-on 這兩個(gè)最常用的指令,提供了特定簡寫:
v-bind 縮寫
<!-- 完整語法 -->
<a v-bind:href="url">...</a>
<!-- 縮寫 -->
<a :href="url">...</a>
v-on 縮寫
<!-- 完整語法 -->
<a v-on:click="doSomething">...</a>
<!-- 縮寫 -->
<a @click="doSomething">...</a>
計(jì)算屬性
對于任何復(fù)雜邏輯,你都應(yīng)當(dāng)使用計(jì)算屬性。
基礎(chǔ)例子
<div id="example">
<p>Original message: "{{ message }}"</p>
<p>Computed reversed message: "{{ reversedMessage }}"</p>
</div>
var vm = new Vue({
el: '#example',
data: {
message: 'Hello'
},
computed: {
// 計(jì)算屬性的 getter
reversedMessage: function () {
// `this` 指向 vm 實(shí)例
return this.message.split('').reverse().join('')
}
}
})
我們提供的函數(shù)將用作屬性 vm.reversedMessage 的 getter 函數(shù):
console.log(vm.reversedMessage) // => 'olleH'
vm.message = 'Goodbye'
console.log(vm.reversedMessage) // => 'eybdooG'
你可以像綁定普通屬性一樣在模板中綁定計(jì)算屬性。Vue 知道 vm.reversedMessage 依賴于 vm.message,因此當(dāng) vm.message 發(fā)生改變時(shí),所有依賴 vm.reversedMessage 的綁定也會更新。而且最妙的是我們已經(jīng)以聲明的方式創(chuàng)建了這種依賴關(guān)系:計(jì)算屬性的 getter 函數(shù)是沒有副作用 (side effect) 的,這使它更易于測試和理解。
計(jì)算屬性緩存 vs 方法
我們可以通過在表達(dá)式中調(diào)用方法來達(dá)到同樣的效果:
<p>Reversed message: "{{ reversedMessage() }}"</p>
// 在組件中
methods: {
reversedMessage: function () {
return this.message.split('').reverse().join('')
}
}
結(jié)果確實(shí)是相同的
然而,不同的是計(jì)算屬性是基于它們的響應(yīng)式依賴進(jìn)行緩存的。只在相關(guān)響應(yīng)式依賴發(fā)生改變時(shí)它們才會重新求值。這就意味著只要 message 還沒有發(fā)生改變,多次訪問 reversedMessage 計(jì)算屬性會立即返回之前的計(jì)算結(jié)果,而不必再次執(zhí)行函數(shù)。
這也同樣意味著下面的計(jì)算屬性將不再更新,因?yàn)?Date.now() 不是響應(yīng)式依賴:
computed: {
now: function () {
return Date.now()
}
}
我們?yōu)槭裁葱枰彺妫考僭O(shè)我們有一個(gè)性能開銷比較大的計(jì)算屬性 A,它需要遍歷一個(gè)巨大的數(shù)組并做大量的計(jì)算。然后我們可能有其他的計(jì)算屬性依賴于 A 。如果沒有緩存,我們將不可避免的多次執(zhí)行 A 的 getter!如果你不希望有緩存,請用方法來替代。
計(jì)算屬性 vs 偵聽屬性(watch)
watch
<div id="demo">{{ fullName }}</div>
var vm = new Vue({
el: '#demo',
data: {
firstName: 'Foo',
lastName: 'Bar',
fullName: 'Foo Bar'
},
watch: {
firstName: function (val) {
this.fullName = val + ' ' + this.lastName
},
lastName: function (val) {
this.fullName = this.firstName + ' ' + val
}
}
})
計(jì)算屬性:
var vm = new Vue({
el: '#demo',
data: {
firstName: 'Foo',
lastName: 'Bar'
},
computed: {
fullName: function () {
return this.firstName + ' ' + this.lastName
}
}
})
總結(jié): computed屬性比watch簡潔
計(jì)算屬性的 setter
// ...
computed: {
fullName: {
// getter
get: function () {
return this.firstName + ' ' + this.lastName
},
// setter
set: function (newValue) {
var names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
}
// ...
現(xiàn)在再運(yùn)行 vm.fullName = 'John Doe' 時(shí),setter 會被調(diào)用,vm.firstName 和 vm.lastName 也會相應(yīng)地被更新。
偵聽器
雖然計(jì)算屬性在大多數(shù)情況下更合適,但有時(shí)也需要一個(gè)自定義的偵聽器。這就是為什么 Vue 通過 watch 選項(xiàng)提供了一個(gè)更通用的方法,來響應(yīng)數(shù)據(jù)的變化。當(dāng)需要在數(shù)據(jù)變化時(shí)執(zhí)行異步或開銷較大的操作時(shí),這個(gè)方式是最有用的。
<div id="watch-example">
<p>
Ask a yes/no question:
<input v-model="question">
</p>
<p>{{ answer }}</p>
</div>
<!-- 因?yàn)?AJAX 庫和通用工具的生態(tài)已經(jīng)相當(dāng)豐富,Vue 核心代碼沒有重復(fù) -->
<!-- 提供這些功能以保持精簡。這也可以讓你自由選擇自己更熟悉的工具。 -->
<script src="https://cdn.jsdelivr.net/npm/axios@0.12.0/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lodash@4.13.1/lodash.min.js"></script>
<script>
var watchExampleVM = new Vue({
el: '#watch-example',
data: {
question: '',
answer: 'I cannot give you an answer until you ask a question!'
},
watch: {
// 如果 `question` 發(fā)生改變,這個(gè)函數(shù)就會運(yùn)行
question: function (newQuestion, oldQuestion) {
this.answer = 'Waiting for you to stop typing...'
this.debouncedGetAnswer()
}
},
created: function () {
// `_.debounce` 是一個(gè)通過 Lodash 限制操作頻率的函數(shù)。
// 在這個(gè)例子中,我們希望限制訪問 yesno.wtf/api 的頻率
// AJAX 請求直到用戶輸入完畢才會發(fā)出。想要了解更多關(guān)于
// `_.debounce` 函數(shù) (及其近親 `_.throttle`) 的知識,
// 請參考:https://lodash.com/docs#debounce
this.debouncedGetAnswer = _.debounce(this.getAnswer, 500)
},
methods: {
getAnswer: function () {
if (this.question.indexOf('?') === -1) {
this.answer = 'Questions usually contain a question mark. ;-)'
return
}
this.answer = 'Thinking...'
var vm = this
axios.get('https://yesno.wtf/api')
.then(function (response) {
vm.answer = _.capitalize(response.data.answer)
})
.catch(function (error) {
vm.answer = 'Error! Could not reach the API. ' + error
})
}
}
})
</script>

Class 與 Style 綁定
在將 v-bind 用于 class 和 style 時(shí),Vue.js 做了專門的增強(qiáng)。表達(dá)式結(jié)果的類型除了字符串之外,還可以是對象或數(shù)組。
可以在對象中傳入更多屬性來動(dòng)態(tài)切換多個(gè) class。此外,v-bind:class 指令也可以與普通的 class 屬性共存。當(dāng)有如下模板:
<div
class="static"
v-bind:class="{ active: isActive, 'text-danger': hasError }"
></div>
和如下 data:
data: {
isActive: true,
hasError: false
}
結(jié)果渲染為:
<div class="static active"></div>
綁定的數(shù)據(jù)對象不必內(nèi)聯(lián)定義在模板里:
<div v-bind:class="classObject"></div>
data: {
classObject: {
active: true,
'text-danger': false
}
}
我們也可以在這里綁定一個(gè)返回對象的計(jì)算屬性
<div v-bind:class="classObject"></div>
data: {
isActive: true,
error: null
},
computed: {
classObject: function () {
return {
active: this.isActive && !this.error,
'text-danger': this.error && this.error.type === 'fatal'
}
}
}
數(shù)組語法
我們可以把一個(gè)數(shù)組傳給 v-bind:class,以應(yīng)用一個(gè) class 列表:
<div v-bind:class="[activeClass, errorClass]"></div>
data: {
activeClass: 'active',
errorClass: 'text-danger'
}
渲染為:
<div class="active text-danger"></div>
三元表達(dá)式:
<div v-bind:class="[isActive ? activeClass : '', errorClass]"></div>
與對象語法結(jié)合:
<div v-bind:class="[{ active: isActive }, errorClass]"></div>
條件渲染
v-else 元素必須緊跟在帶 v-if 或者 v-else-if 的元素的后面,否則它將不會被識別。
v-else-if
2.1.0 新增
v-else-if,顧名思義,充當(dāng) v-if 的“else-if 塊”,可以連續(xù)使用:
<div v-if="type === 'A'">
A
</div>
<div v-else-if="type === 'B'">
B
</div>
<div v-else-if="type === 'C'">
C
</div>
<div v-else>
Not A/B/C
</div>
用 key 管理可復(fù)用的元素
Vue 會盡可能高效地渲染元素,通常會復(fù)用已有元素而不是從頭開始渲染。這么做除了使 Vue 變得非常快之外,還有其它一些好處。
<template v-if="loginType === 'username'">
<label>Username</label>
<input placeholder="Enter your username">
</template>
<template v-else>
<label>Email</label>
<input placeholder="Enter your email address">
</template>
那么在上面的代碼中切換 loginType 將不會清除用戶已經(jīng)輸入的內(nèi)容。因?yàn)閮蓚€(gè)模板使用了相同的元素,<input> 不會被替換掉——僅僅是替換了它的 placeholder。
這樣也不總是符合實(shí)際需求,所以 Vue 為你提供了一種方式來表達(dá)“這兩個(gè)元素是完全獨(dú)立的,不要復(fù)用它們”。只需添加一個(gè)具有唯一值的 key 屬性即可:
<template v-if="loginType === 'username'">
<label>Username</label>
<input placeholder="Enter your username" key="username-input">
</template>
<template v-else>
<label>Email</label>
<input placeholder="Enter your email address" key="email-input">
</template>
v-show
另一個(gè)用于根據(jù)條件展示元素的選項(xiàng)是 v-show 指令。用法大致一樣:
<h1 v-show="ok">Hello!</h1>
不同的是帶有 v-show 的元素始終會被渲染并保留在 DOM 中。v-show 只是簡單地切換元素的 CSS 屬性 display
v-if是“真正”的條件渲染,因?yàn)樗鼤_保在切換過程中條件塊內(nèi)的事件監(jiān)聽器和子組件適當(dāng)?shù)乇讳N毀和重建。
相比之下,v-show就簡單得多——不管初始條件是什么,元素總是會被渲染,并且只是簡單地基于 CSS 進(jìn)行切換。
一般來說,v-if有更高的切換開銷,而 v-show 有更高的初始渲染開銷。因此,如果需要非常頻繁地切換,則使用 v-show 較好;如果在運(yùn)行時(shí)條件很少改變,則使用 v-if 較好。
事件處理
有時(shí)也需要在內(nèi)聯(lián)語句處理器中訪問原始的 DOM 事件??梢杂锰厥庾兞?$event 把它傳入方法:
<button v-on:click="warn('Form cannot be submitted yet.', $event)">
Submit
</button>
// ...
methods: {
warn: function (message, event) {
// 現(xiàn)在我們可以訪問原生事件對象
if (event) event.preventDefault()
alert(message)
}
}
事件修飾符
在事件處理程序中調(diào)用 event.preventDefault() 或 event.stopPropagation() 是非常常見的需求。盡管我們可以在方法中輕松實(shí)現(xiàn)這點(diǎn),但更好的方式是:方法只有純粹的數(shù)據(jù)邏輯,而不是去處理 DOM 事件細(xì)節(jié)。
.stop.prevent.capture.self.once.passive
<!-- 阻止單擊事件繼續(xù)傳播 -->
<a v-on:click.stop="doThis"></a>
<!-- 提交事件不再重載頁面 -->
<form v-on:submit.prevent="onSubmit"></form>
<!-- 修飾符可以串聯(lián) -->
<a v-on:click.stop.prevent="doThat"></a>
<!-- 只有修飾符 -->
<form v-on:submit.prevent></form>
<!-- 添加事件監(jiān)聽器時(shí)使用事件捕獲模式 -->
<!-- 即元素自身觸發(fā)的事件先在此處理,然后才交由內(nèi)部元素進(jìn)行處理 -->
<div v-on:click.capture="doThis">...</div>
<!-- 只當(dāng)在 event.target 是當(dāng)前元素自身時(shí)觸發(fā)處理函數(shù) -->
<!-- 即事件不是從內(nèi)部元素觸發(fā)的 -->
<div v-on:click.self="doThat">...</div>
使用修飾符時(shí),順序很重要;相應(yīng)的代碼會以同樣的順序產(chǎn)生。因此,用 v-on:click.prevent.self 會阻止所有的點(diǎn)擊,而 v-on:click.self.prevent 只會阻止對元素自身的點(diǎn)擊。
.once修飾符
2.1.4 新增
<!-- 點(diǎn)擊事件將只會觸發(fā)一次 -->
<a v-on:click.once="doThis"></a>
不像其它只能對原生的 DOM 事件起作用的修飾符,.once 修飾符還能被用到自定義的組件事件上。
.passive修飾符
<!-- 滾動(dòng)事件的默認(rèn)行為 (即滾動(dòng)行為) 將會立即觸發(fā) -->
<!-- 而不會等待 `onScroll` 完成 -->
<!-- 這其中包含 `event.preventDefault()` 的情況 -->
<div v-on:scroll.passive="onScroll">...</div>
Vue 還對應(yīng)addEventListener 中的 passive 選項(xiàng)提供了 .passive 修飾符。
這個(gè) .passive 修飾符尤其能夠提升移動(dòng)端的性能。
不要把 .passive 和 .prevent 一起使用,因?yàn)?.prevent 將會被忽略,同時(shí)瀏覽器可能會向你展示一個(gè)警告。請記住,.passive 會告訴瀏覽器你不想阻止事件的默認(rèn)行為。
按鍵修飾符
在監(jiān)聽鍵盤事件時(shí),我們經(jīng)常需要檢查詳細(xì)的按鍵。Vue 允許為 v-on在監(jiān)聽鍵盤事件時(shí)添加按鍵修飾符:
<!-- 只有在 `key` 是 `Enter` 時(shí)調(diào)用 `vm.submit()` -->
<input v-on:keyup.enter="submit">
你可以直接將KeyboardEvent.key暴露的任意有效按鍵名轉(zhuǎn)換為 kebab-case 來作為修飾符。
<input v-on:keyup.page-down="onPageDown">
系統(tǒng)修飾鍵
2.1.0 新增
可以用如下修飾符來實(shí)現(xiàn)僅在按下相應(yīng)按鍵時(shí)才觸發(fā)鼠標(biāo)或鍵盤事件的監(jiān)聽器。
.ctrl.alt.shift.meta
注意:在 Mac 系統(tǒng)鍵盤上,meta 對應(yīng) command 鍵 (?)。在 Windows 系統(tǒng)鍵盤 meta 對應(yīng) Windows 徽標(biāo)鍵 (?)。在 Sun 操作系統(tǒng)鍵盤上,meta 對應(yīng)實(shí)心寶石鍵 (◆)。在其他特定鍵盤上,尤其在 MIT 和 Lisp 機(jī)器的鍵盤、以及其后繼產(chǎn)品,比如 Knight 鍵盤、space-cadet 鍵盤,meta 被標(biāo)記為“META”。在 Symbolics 鍵盤上,meta 被標(biāo)記為“META”或者“Meta”。
例如:
<!-- Alt + C -->
<input @keyup.alt.67="clear">
<!-- Ctrl + Click -->
<div @click.ctrl="doSomething">Do something</div>
請注意修飾鍵與常規(guī)按鍵不同,在和 keyup 事件一起用時(shí),事件觸發(fā)時(shí)修飾鍵必須處于按下狀態(tài)。換句話說,只有在按住 ctrl 的情況下釋放其它按鍵,才能觸發(fā) keyup.ctrl。而單單釋放 ctrl 也不會觸發(fā)事件。如果你想要這樣的行為,請為 ctrl 換用 keyCode:keyup.17。
.exact 修飾符
2.5.0 新增
.exact 修飾符允許你控制由精確的系統(tǒng)修飾符組合觸發(fā)的事件。
<!-- 即使 Alt 或 Shift 被一同按下時(shí)也會觸發(fā) -->
<button @click.ctrl="onClick">A</button>
<!-- 有且只有 Ctrl 被按下的時(shí)候才觸發(fā) -->
<button @click.ctrl.exact="onCtrlClick">A</button>
<!-- 沒有任何系統(tǒng)修飾符被按下的時(shí)候才觸發(fā) -->
<button @click.exact="onClick">A</button>
鼠標(biāo)按鈕修飾符
2.2.0 新增
.left.right.middle
為什么在 HTML 中監(jiān)聽事件?
你可能注意到這種事件監(jiān)聽的方式違背了關(guān)注點(diǎn)分離 (separation of concern) 這個(gè)長期以來的優(yōu)良傳統(tǒng)。但不必?fù)?dān)心,因?yàn)樗械?Vue.js 事件處理方法和表達(dá)式都嚴(yán)格綁定在當(dāng)前視圖的 ViewModel 上,它不會導(dǎo)致任何維護(hù)上的困難。實(shí)際上,使用 v-on 有幾個(gè)好處:
1、掃一眼 HTML 模板便能輕松定位在 JavaScript 代碼里對應(yīng)的方法。
2、因?yàn)槟銦o須在 JavaScript 里手動(dòng)綁定事件,你的 ViewModel 代碼可以是非常純粹的邏輯,和 DOM 完全解耦,更易于測試。
3、當(dāng)一個(gè) ViewModel 被銷毀時(shí),所有的事件處理器都會自動(dòng)被刪除。你無須擔(dān)心如何清理它們。
組件基礎(chǔ)
組件里的data 必須是一個(gè)函數(shù)
當(dāng)我們定義這個(gè) <button-counter> 組件時(shí),你可能會發(fā)現(xiàn)它的 data 并不是像這樣直接提供一個(gè)對象:
data: {
count: 0
}
取而代之的是,一個(gè)組件的 data 選項(xiàng)必須是一個(gè)函數(shù),因此每個(gè)實(shí)例可以維護(hù)一份被返回對象的獨(dú)立的拷貝:
data: function () {
return {
count: 0
}
}
如果 Vue 沒有這條規(guī)則,點(diǎn)擊一個(gè)按鈕就可能會像如下代碼一樣影響到其它所有實(shí)例。
通過 Prop 向子組件傳遞數(shù)據(jù)
早些時(shí)候,我們提到了創(chuàng)建一個(gè)博文組件的事情。問題是如果你不能向這個(gè)組件傳遞某一篇博文的標(biāo)題或內(nèi)容之類的我們想展示的數(shù)據(jù)的話,它是沒有辦法使用的。這也正是 prop 的由來。
Prop 是你可以在組件上注冊的一些自定義特性。當(dāng)一個(gè)值傳遞給一個(gè) prop 特性的時(shí)候,它就變成了那個(gè)組件實(shí)例的一個(gè)屬性。為了給博文組件傳遞一個(gè)標(biāo)題,我們可以用一個(gè) props 選項(xiàng)將其包含在該組件可接受的 prop 列表中:
Vue.component('blog-post', {
props: ['title'],
template: '<h3>{{ title }}</h3>'
})
一個(gè) prop 被注冊之后,你就可以像這樣把數(shù)據(jù)作為一個(gè)自定義特性傳遞進(jìn)來:
<blog-post title="My journey with Vue"></blog-post>
<blog-post title="Blogging with Vue"></blog-post>
<blog-post title="Why Vue is so fun"></blog-post>
我們可以使用 v-bind 來動(dòng)態(tài)傳遞 prop。這在你一開始不清楚要渲染的具體內(nèi)容,比如從一個(gè) API 獲取博文列表的時(shí)候,是非常有用的。
new Vue({
el: '#blog-post-demo',
data: {
posts: [
{ id: 1, title: 'My journey with Vue' },
{ id: 2, title: 'Blogging with Vue' },
{ id: 3, title: 'Why Vue is so fun' }
]
}
})
<blog-post
v-for="post in posts"
v-bind:key="post.id"
v-bind:title="post.title"
></blog-post>
看起來當(dāng)組件變得越來越復(fù)雜的時(shí)候,我們的博文不只需要標(biāo)題和內(nèi)容,還需要發(fā)布日期、評論等等。為每個(gè)相關(guān)的信息定義一個(gè) prop 會變得很麻煩:
<blog-post
v-for="post in posts"
v-bind:key="post.id"
v-bind:title="post.title"
v-bind:content="post.content"
v-bind:publishedAt="post.publishedAt"
v-bind:comments="post.comments"
></blog-post>
所以是時(shí)候重構(gòu)一下這個(gè) <blog-post> 組件了,讓它變成接受一個(gè)單獨(dú)的 post prop:
<blog-post
v-for="post in posts"
v-bind:key="post.id"
v-bind:post="post"
></blog-post>
Vue.component('blog-post', {
props: ['post'],
template: `
<div class="blog-post">
<h3>{{ post.title }}</h3>
<div v-html="post.content"></div>
</div>
`
})
上述的這個(gè)和一些接下來的示例使用了 JavaScript 的模板字符串來讓多行的模板更易讀。它們在 IE 下并沒有被支持,所以如果你需要在不 (經(jīng)過 Babel 或 TypeScript 之類的工具) 編譯的情況下支持 IE,請使用折行轉(zhuǎn)義字符取而代之。
折行轉(zhuǎn)義字符
This works:
var htmlString = "<div>This is a string.</div>";
This fails:
var htmlSTring = "<div>
This is a string.
</div>";
Add backslashes to get it to work:
var htmlSTring = "<div>\
This is a string.\
</div>";
監(jiān)聽子組件事件
在我們開發(fā) <blog-post> 組件時(shí),它的一些功能可能要求我們和父級組件進(jìn)行溝通。例如我們可能會引入一個(gè)輔助功能來放大博文的字號,同時(shí)讓頁面的其它部分保持默認(rèn)的字號。
在其父組件中,我們可以通過添加一個(gè) postFontSize 數(shù)據(jù)屬性來支持這個(gè)功能:
new Vue({
el: '#blog-posts-events-demo',
data: {
posts: [/* ... */],
postFontSize: 1
}
})
它可以在模板中用來控制所有博文的字號:
<div id="blog-posts-events-demo">
<div :style="{ fontSize: postFontSize + 'em' }">
<blog-post
v-for="post in posts"
v-bind:key="post.id"
v-bind:post="post"
></blog-post>
</div>
</div>
現(xiàn)在我們在每篇博文正文之前添加一個(gè)按鈕來放大字號:
Vue.component('blog-post', {
props: ['post'],
template: `
<div class="blog-post">
<h3>{{ post.title }}</h3>
<button>
Enlarge text
</button>
<div v-html="post.content"></div>
</div>
`
})
問題是這個(gè)按鈕不會做任何事:
<button>
Enlarge text
</button>
當(dāng)點(diǎn)擊這個(gè)按鈕時(shí),我們需要告訴父級組件放大所有博文的文本。幸好 Vue 實(shí)例提供了一個(gè)自定義事件的系統(tǒng)來解決這個(gè)問題。父級組件可以像處理 native DOM 事件一樣通過v-on監(jiān)聽子組件實(shí)例的任意事件:
<blog-post
...
v-on:enlarge-text="postFontSize += 0.1"
></blog-post>
同時(shí)子組件可以通過調(diào)用內(nèi)建的$emit并傳入事件名稱來觸發(fā)一個(gè)事件:
<button v-on:click="$emit('enlarge-text')">
Enlarge text
</button>
有點(diǎn)想吐槽,如果要修改字體大小在父組件里修改不就行了,這例子太牽強(qiáng)
使用事件拋出一個(gè)值
有的時(shí)候用一個(gè)事件來拋出一個(gè)特定的值是非常有用的。例如我們可能想讓 <blog-post> 組件決定它的文本要放大多少。這時(shí)可以使用 $emit 的第二個(gè)參數(shù)來提供這個(gè)值:
<button v-on:click="$emit('enlarge-text', 0.1)">
Enlarge text
</button>
然后當(dāng)在父級組件監(jiān)聽這個(gè)事件的時(shí)候,我們可以通過 $event 訪問到被拋出的這個(gè)值:
<blog-post
...
v-on:enlarge-text="postFontSize += $event"
></blog-post>
或者,如果這個(gè)事件處理函數(shù)是一個(gè)方法:
<blog-post
...
v-on:enlarge-text="onEnlargeText"
></blog-post>
那么這個(gè)值將會作為第一個(gè)參數(shù)傳入這個(gè)方法:
methods: {
onEnlargeText: function (enlargeAmount) {
this.postFontSize += enlargeAmount
}
}
在組件上使用 v-model
自定義事件也可以用于創(chuàng)建支持 v-model 的自定義輸入組件。記?。?/p>
<input v-model="searchText">
等價(jià)于:
<input
v-bind:value="searchText"
v-on:input="searchText = $event.target.value"
>
當(dāng)用在組件上時(shí),v-model 則會這樣:
<custom-input
v-bind:value="searchText"
v-on:input="searchText = $event"
></custom-input>
為了讓它正常工作,這個(gè)組件內(nèi)的 <input> 必須:
- 將其 value 特性綁定到一個(gè)名叫 value 的 prop 上
- 在其 input 事件被觸發(fā)時(shí),將新的值通過自定義的 input 事件拋出
Vue.component('custom-input', {
props: ['value'],
template: `
<input
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
>
`
})
現(xiàn)在 v-model 就應(yīng)該可以在這個(gè)組件上完美地工作起來了:
<custom-input v-model="searchText"></custom-input>
自定義事件
事件名
不同于組件和 prop,事件名不存在任何自動(dòng)化的大小寫轉(zhuǎn)換。而是觸發(fā)的事件名需要完全匹配監(jiān)聽這個(gè)事件所用的名稱。舉個(gè)例子,如果觸發(fā)一個(gè) camelCase 名字的事件:
命名規(guī)則
camelCased (駝峰式)
kebab-case(短橫線命名)
PascalCase(帕斯卡拼寫法)
this.$emit('myEvent')
則監(jiān)聽這個(gè)名字的 kebab-case 版本是不會有任何效果的:
<!-- 沒有效果 -->
<my-component v-on:my-event="doSomething"></my-component>
不同于組件和 prop,事件名不會被用作一個(gè) JavaScript 變量名或?qū)傩悦跃蜎]有理由使用 camelCase 或 PascalCase 了。并且 v-on 事件監(jiān)聽器在 DOM 模板中會被自動(dòng)轉(zhuǎn)換為全小寫 (因?yàn)?HTML 是大小寫不敏感的),所以 v-on:myEvent 將會變成 v-on:myevent——導(dǎo)致 myEvent 不可能被監(jiān)聽到。
自定義組件的 v-model
2.2.0+ 新增
一個(gè)組件上的 v-model 默認(rèn)會利用名為 value 的 prop 和名為 input 的事件,但是像單選框、復(fù)選框等類型的輸入控件可能會將 value 特性用于不同的目的。model 選項(xiàng)可以用來避免這樣的沖突:
Vue.component('base-checkbox', {
model: {
prop: 'checked',
event: 'change'
},
props: {
checked: Boolean
},
template: `
<input
type="checkbox"
v-bind:checked="checked"
v-on:change="$emit('change', $event.target.checked)"
>
`
})
現(xiàn)在在這個(gè)組件上使用 v-model 的時(shí)候:
<base-checkbox v-model="lovingVue"></base-checkbox>
這里的 lovingVue 的值將會傳入這個(gè)名為 checked 的 prop。同時(shí)當(dāng) <base-checkbox> 觸發(fā)一個(gè) change 事件并附帶一個(gè)新的值的時(shí)候,這個(gè) lovingVue 的屬性將會被更新。
注意你仍然需要在組件的 props 選項(xiàng)里聲明 checked 這個(gè) prop。
暫時(shí)理解不來這塊內(nèi)容
將原生事件綁定到組件
你可能有很多次想要在一個(gè)組件的根元素上直接監(jiān)聽一個(gè)原生事件。這時(shí),你可以使用 v-on 的 .native 修飾符:
<base-input v-on:focus.native="onFocus"></base-input>
在有的時(shí)候這是很有用的,不過在你嘗試監(jiān)聽一個(gè)類似 <input> 的非常特定的元素時(shí),這并不是個(gè)好主意。比如上述 <base-input> 組件可能做了如下重構(gòu),所以根元素實(shí)際上是一個(gè) <label> 元素:
<label>
{{ label }}
<input
v-bind="$attrs"
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
>
</label>
這時(shí),父級的 .native 監(jiān)聽器將靜默失敗。它不會產(chǎn)生任何報(bào)錯(cuò),但是 onFocus 處理函數(shù)不會如你預(yù)期地被調(diào)用。
為了解決這個(gè)問題,Vue 提供了一個(gè) $listeners 屬性,它是一個(gè)對象,里面包含了作用在這個(gè)組件上的所有監(jiān)聽器。例如:
{
focus: function (event) { /* ... */ }
input: function (value) { /* ... */ },
}
有了這個(gè)$listeners屬性,你就可以配合 v-on="$listeners" 將所有的事件監(jiān)聽器指向這個(gè)組件的某個(gè)特定的子元素。對于類似 <input> 的你希望它也可以配合 v-model 工作的組件來說,為這些監(jiān)聽器創(chuàng)建一個(gè)類似下述 inputListeners 的計(jì)算屬性通常是非常有用的:
Vue.component('base-input', {
inheritAttrs: false,
props: ['label', 'value'],
computed: {
inputListeners: function () {
var vm = this
// `Object.assign` 將所有的對象合并為一個(gè)新對象
return Object.assign({},
// 我們從父級添加所有的監(jiān)聽器
this.$listeners,
// 然后我們添加自定義監(jiān)聽器,
// 或覆寫一些監(jiān)聽器的行為
{
// 這里確保組件配合 `v-model` 的工作
input: function (event) {
vm.$emit('input', event.target.value)
}
}
)
}
},
template: `
<label>
{{ label }}
<input
v-bind="$attrs"
v-bind:value="value"
v-on="inputListeners"
>
</label>
`
})
現(xiàn)在 <base-input> 組件是一個(gè)完全透明的包裹器了,也就是說它可以完全像一個(gè)普通的 <input> 元素一樣使用了:所有跟它相同的特性和監(jiān)聽器的都可以工作。
.sync 修飾符
對一個(gè) prop 進(jìn)行“雙向綁定”
推薦以update:myPropName的模式觸發(fā)事件取而代之。舉個(gè)例子,在一個(gè)包含 title prop 的假設(shè)的組件中,我們可以用以下方法表達(dá)對其賦新值的意圖:
this.$emit('update:title', newTitle)
然后父組件可以監(jiān)聽那個(gè)事件并根據(jù)需要更新一個(gè)本地的數(shù)據(jù)屬性。例如:
<text-document
v-bind:title="doc.title"
v-on:update:title="doc.title = $event"
></text-document>
為了方便起見,我們?yōu)檫@種模式提供一個(gè)縮寫,即 .sync 修飾符:
<text-document v-bind:title.sync="doc.title"></text-document>
注意帶有 .sync 修飾符的 v-bind 不能和表達(dá)式一起使用 (例如 v-bind:title.sync=”doc.title + ‘!’” 是無效的)。取而代之的是,你只能提供你想要綁定的屬性名,類似 v-model。
當(dāng)我們用一個(gè)對象同時(shí)設(shè)置多個(gè) prop 的時(shí)候,也可以將這個(gè) .sync 修飾符和 v-bind 配合使用:
<text-document v-bind.sync="doc"></text-document>
這樣會把 doc 對象中的每一個(gè)屬性 (如 title) 都作為一個(gè)獨(dú)立的 prop 傳進(jìn)去,然后各自添加用于更新的 v-on 監(jiān)聽器。
將 v-bind.sync 用在一個(gè)字面量的對象上,例如 v-bind.sync=”{ title: doc.title }”,是無法正常工作的,因?yàn)樵诮馕鲆粋€(gè)像這樣的復(fù)雜表達(dá)式的時(shí)候,有很多邊緣情況需要考慮。
插槽
插槽內(nèi)容
它允許你像這樣合成組件:
<navigation-link url="/profile">
Your Profile
</navigation-link>
然后你在<navigation-link>的模板中可能會寫為:
<a
v-bind:href="url"
class="nav-link"
>
<slot></slot>
</a>
當(dāng)組件渲染的時(shí)候,<slot></slot>將會被替換為“Your Profile”。插槽內(nèi)可以包含任何模板代碼,包括 HTML:
<navigation-link url="/profile">
<!-- 添加一個(gè) Font Awesome 圖標(biāo) -->
<span class="fa fa-user"></span>
Your Profile
</navigation-link>
甚至其它的組件:
<navigation-link url="/profile">
<!-- 添加一個(gè)圖標(biāo)的組件 -->
<font-awesome-icon name="user"></font-awesome-icon>
Your Profile
</navigation-link>
如果<navigation-link>沒有包含一個(gè) <slot> 元素,則該組件起始標(biāo)簽和結(jié)束標(biāo)簽之間的任何內(nèi)容都會被拋棄。
編譯作用域
當(dāng)你想在一個(gè)插槽中使用數(shù)據(jù)時(shí),例如:
<navigation-link url="/profile">
Logged in as {{ user.name }}
</navigation-link>
該插槽跟模板的其它地方一樣可以訪問相同的實(shí)例屬性 (也就是相同的“作用域”),而不能訪問<navigation-link>的作用域。例如 url 是訪問不到的:
<navigation-link url="/profile">
Clicking here will send you to: {{ url }}
<!--
這里的 `url` 會是 undefined,因?yàn)?"/profile" 是
_傳遞給_ <navigation-link> 的而不是
在 <navigation-link> 組件*內(nèi)部*定義的。
-->
</navigation-link>
作為一條規(guī)則,請記?。?/p>
父級模板里的所有內(nèi)容都是在父級作用域中編譯的;子模板里的所有內(nèi)容都是在子作用域中編譯的。
后備內(nèi)容
有時(shí)為一個(gè)插槽設(shè)置具體的后備 (也就是默認(rèn)的) 內(nèi)容是很有用的,它只會在沒有提供內(nèi)容的時(shí)候被渲染。例如在一個(gè) <submit-button> 組件中:
<button type="submit">
<slot></slot>
</button>
我們可能希望這個(gè) <button> 內(nèi)絕大多數(shù)情況下都渲染文本“Submit”。為了將“Submit”作為后備內(nèi)容,我們可以將它放在 <slot> 標(biāo)簽內(nèi):
<button type="submit">
<slot>Submit</slot>
</button>
現(xiàn)在當(dāng)我在一個(gè)父級組件中使用<submit-button>并且不提供任何插槽內(nèi)容時(shí):
<submit-button></submit-button>
后備內(nèi)容“Submit”將會被渲染:
<button type="submit">
Submit
</button>
但是如果我們提供內(nèi)容:
<submit-button>
Save
</submit-button>
則這個(gè)提供的內(nèi)容將會被渲染從而取代后備內(nèi)容:
<button type="submit">
Save
</button>
具名插槽
有時(shí)我們需要多個(gè)插槽。例如對于一個(gè)帶有如下模板的 <base-layout> 組件:
<div class="container">
<header>
<!-- 我們希望把頁頭放這里 -->
</header>
<main>
<!-- 我們希望把主要內(nèi)容放這里 -->
</main>
<footer>
<!-- 我們希望把頁腳放這里 -->
</footer>
</div>
對于這樣的情況,<slot> 元素有一個(gè)特殊的特性:name。這個(gè)特性可以用來定義額外的插槽:
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
一個(gè)不帶 name 的 <slot> 出口會帶有隱含的名字“default”。
在向具名插槽提供內(nèi)容的時(shí)候,我們可以在一個(gè) <template> 元素上使用 v-slot 指令,并以 v-slot 的參數(shù)的形式提供其名稱:
<base-layout>
<template v-slot:header>
<h1>Here might be a page title</h1>
</template>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
<template v-slot:footer>
<p>Here's some contact info</p>
</template>
</base-layout>
現(xiàn)在<template>元素中的所有內(nèi)容都將會被傳入相應(yīng)的插槽。任何沒有被包裹在帶有 v-slot 的 <template>中的內(nèi)容都會被視為默認(rèn)插槽的內(nèi)容。
然而,如果你希望更明確一些,仍然可以在一個(gè) <template> 中包裹默認(rèn)插槽的內(nèi)容:
<base-layout>
<template v-slot:header>
<h1>Here might be a page title</h1>
</template>
<template v-slot:default>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</template>
<template v-slot:footer>
<p>Here's some contact info</p>
</template>
</base-layout>
任何一種寫法都會渲染出:
<div class="container">
<header>
<h1>Here might be a page title</h1>
</header>
<main>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</main>
<footer>
<p>Here's some contact info</p>
</footer>
</div>
注意 v-slot 只能添加在一個(gè) <template> 上
作用域插槽
有時(shí)讓插槽內(nèi)容能夠訪問子組件中才有的數(shù)據(jù)是很有用的。例如,設(shè)想一個(gè)帶有如下模板的<current-user>組件:
<span>
<slot>{{ user.lastName }}</slot>
</span>
我們想讓它的后備內(nèi)容顯示用戶的名,以取代正常情況下用戶的姓,如下:
<current-user>
{{ user.firstName }}
</current-user>
然而上述代碼不會正常工作,因?yàn)橹挥?<current-user> 組件可以訪問到 user 而我們提供的內(nèi)容是在父級渲染的。
為了讓 user 在父級的插槽內(nèi)容可用,我們可以將 user 作為 <slot> 元素的一個(gè)特性綁定上去:
<span>
<slot v-bind:user="user">
{{ user.lastName }}
</slot>
</span>
綁定在 <slot> 元素上的特性被稱為插槽 prop?,F(xiàn)在在父級作用域中,我們可以給 v-slot 帶一個(gè)值來定義我們提供的插槽 prop 的名字:
<current-user>
<template v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</template>
</current-user>
在這個(gè)例子中,我們選擇將包含所有插槽 prop 的對象命名為 slotProps,但你也可以使用任意你喜歡的名字。
獨(dú)占默認(rèn)插槽的縮寫語法
在上述情況下,當(dāng)被提供的內(nèi)容只有默認(rèn)插槽時(shí),組件的標(biāo)簽才可以被當(dāng)作插槽的模板來使用。這樣我們就可以把 v-slot 直接用在組件上:
<current-user v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</current-user>
這種寫法還可以更簡單。就像假定未指明的內(nèi)容對應(yīng)默認(rèn)插槽一樣,不帶參數(shù)的 v-slot 被假定對應(yīng)默認(rèn)插槽:
<current-user v-slot="slotProps">
{{ slotProps.user.firstName }}
</current-user>
只能單獨(dú)使用
注意默認(rèn)插槽的縮寫語法不能和具名插槽混用,因?yàn)樗鼤?dǎo)致作用域不明確:
<!-- 無效,會導(dǎo)致警告 -->
<current-user v-slot="slotProps">
{{ slotProps.user.firstName }}
<template v-slot:other="otherSlotProps">
slotProps is NOT available here
</template>
</current-user>
只要出現(xiàn)多個(gè)插槽,請始終為所有的插槽使用完整的基于 <template> 的語法:
<current-user>
<template v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</template>
<template v-slot:other="otherSlotProps">
...
</template>
</current-user>
解構(gòu)插槽 Prop
作用域插槽的內(nèi)部工作原理是將你的插槽內(nèi)容包括在一個(gè)傳入單個(gè)參數(shù)的函數(shù)里:
function (slotProps) {
// 插槽內(nèi)容
}
這意味著 v-slot 的值實(shí)際上可以是任何能夠作為函數(shù)定義中的參數(shù)的 JavaScript 表達(dá)式。所以在支持的環(huán)境下 (單文件組件或現(xiàn)代瀏覽器),你也可以使用ES2015 解構(gòu)來傳入具體的插槽 prop,如下:
<current-user v-slot="{ user }">
{{ user.firstName }}
</current-user>
這樣可以使模板更簡潔,尤其是在該插槽提供了多個(gè) prop 的時(shí)候。它同樣開啟了 prop 重命名等其它可能,例如將 user 重命名為 person:
<current-user v-slot="{ user: person }">
{{ person.firstName }}
</current-user>
你甚至可以定義后備內(nèi)容,用于插槽 prop 是 undefined 的情形:
<current-user v-slot="{ user = { firstName: 'Guest' } }">
{{ user.firstName }}
</current-user>
動(dòng)態(tài)插槽名
動(dòng)態(tài)指令參數(shù)也可以用在 v-slot 上,來定義動(dòng)態(tài)的插槽名:
<base-layout>
<template v-slot:[dynamicSlotName]>
...
</template>
</base-layout>
具名插槽的縮寫
跟 v-on 和 v-bind 一樣,v-slot 也有縮寫,即把參數(shù)之前的所有內(nèi)容 (v-slot:) 替換為字符 #。例如 v-slot:header 可以被重寫為 #header:
<base-layout>
<template #header>
<h1>Here might be a page title</h1>
</template>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
<template #footer>
<p>Here's some contact info</p>
</template>
</base-layout>
然而,和其它指令一樣,該縮寫只在其有參數(shù)的時(shí)候才可用。這意味著以下語法是無效的:
<!-- 這樣會觸發(fā)一個(gè)警告 -->
<current-user #="{ user }">
{{ user.firstName }}
</current-user>
如果你希望使用縮寫的話,你必須始終以明確插槽名取而代之:
<current-user #default="{ user }">
{{ user.firstName }}
</current-user>
動(dòng)態(tài)組件 & 異步組件
在動(dòng)態(tài)組件上使用 keep-alive
我們之前曾經(jīng)在一個(gè)多標(biāo)簽的界面中使用 is 特性來切換不同的組件:
<component v-bind:is="currentTabComponent"></component>
當(dāng)在這些組件之間切換的時(shí)候,你有時(shí)會想保持這些組件的狀態(tài),以避免反復(fù)重渲染導(dǎo)致的性能問題。
你會注意到,如果你選擇了一篇文章,切換到 Archive 標(biāo)簽,然后再切換回 Posts,是不會繼續(xù)展示你之前選擇的文章的。這是因?yàn)槟忝看吻袚Q新標(biāo)簽的時(shí)候,Vue 都創(chuàng)建了一個(gè)新的 currentTabComponent 實(shí)例。
但是在這個(gè)案例中,我們更希望那些標(biāo)簽的組件實(shí)例能夠被在它們第一次被創(chuàng)建的時(shí)候緩存下來。為了解決這個(gè)問題,我們可以用一個(gè) <keep-alive> 元素將其動(dòng)態(tài)組件包裹起來。
<keep-alive>
<component v-bind:is="currentTabComponent"></component>
</keep-alive>
keep-alive
-
Props:
- include - 字符串或正則表達(dá)式。只有名稱匹配的組件會被緩存。
- exclude - 字符串或正則表達(dá)式。任何名稱匹配的組件都不會被緩存。
- max - 數(shù)字。最多可以緩存多少組件實(shí)例。
用法:
<keep-alive> 包裹動(dòng)態(tài)組件時(shí),會緩存不活動(dòng)的組件實(shí)例,而不是銷毀它們。和 <transition> 相似,<keep-alive> 是一個(gè)抽象組件:它自身不會渲染一個(gè) DOM 元素,也不會出現(xiàn)在父組件鏈中。
當(dāng)組件在 <keep-alive> 內(nèi)被切換,它的 activated 和 deactivated 這兩個(gè)生命周期鉤子函數(shù)將會被對應(yīng)執(zhí)行。
在 2.2.0 及其更高版本中,activated 和 deactivated 將會在 <keep-alive> 樹內(nèi)的所有嵌套組件中觸發(fā)。
主要用于保留組件狀態(tài)或避免重新渲染。
<!-- 基本 -->
<keep-alive>
<component :is="view"></component>
</keep-alive>
<!-- 多個(gè)條件判斷的子組件 -->
<keep-alive>
<comp-a v-if="a > 1"></comp-a>
<comp-b v-else></comp-b>
</keep-alive>
<!-- 和 `<transition>` 一起使用 -->
<transition>
<keep-alive>
<component :is="view"></component>
</keep-alive>
</transition>
注意,<keep-alive> 是用在其一個(gè)直屬的子組件被開關(guān)的情形。如果你在其中有 v-for 則不會工作。如果有上述的多個(gè)條件性的子元素,<keep-alive> 要求同時(shí)只有一個(gè)子元素被渲染。
異步組件
在大型應(yīng)用中,我們可能需要將應(yīng)用分割成小一些的代碼塊,并且只在需要的時(shí)候才從服務(wù)器加載一個(gè)模塊。為了簡化,Vue 允許你以一個(gè)工廠函數(shù)的方式定義你的組件,這個(gè)工廠函數(shù)會異步解析你的組件定義。Vue 只有在這個(gè)組件需要被渲染的時(shí)候才會觸發(fā)該工廠函數(shù),且會把結(jié)果緩存起來供未來重渲染。例如:
Vue.component('async-example', function (resolve, reject) {
setTimeout(function () {
// 向 `resolve` 回調(diào)傳遞組件定義
resolve({
template: '<div>I am async!</div>'
})
}, 1000)
})
如你所見,這個(gè)工廠函數(shù)會收到一個(gè) resolve 回調(diào),這個(gè)回調(diào)函數(shù)會在你從服務(wù)器得到組件定義的時(shí)候被調(diào)用。你也可以調(diào)用 reject(reason) 來表示加載失敗。這里的 setTimeout 是為了演示用的,如何獲取組件取決于你自己。一個(gè)推薦的做法是將異步組件和 webpack 的 code-splitting 功能一起配合使用:
Vue.component('async-webpack-example', function (resolve) {
// 這個(gè)特殊的 `require` 語法將會告訴 webpack
// 自動(dòng)將你的構(gòu)建代碼切割成多個(gè)包,這些包
// 會通過 Ajax 請求加載
require(['./my-async-component'], resolve)
})
你也可以在工廠函數(shù)中返回一個(gè) Promise,所以把 webpack 2 和 ES2015 語法加在一起,我們可以寫成這樣:
Vue.component(
'async-webpack-example',
// 這個(gè) `import` 函數(shù)會返回一個(gè) `Promise` 對象。
() => import('./my-async-component')
)
當(dāng)使用局部注冊的時(shí)候,你也可以直接提供一個(gè)返回 Promise 的函數(shù):
new Vue({
// ...
components: {
'my-component': () => import('./my-async-component')
}
})
如果你是一個(gè) Browserify 用戶同時(shí)喜歡使用異步組件,很不幸這個(gè)工具的作者明確表示異步加載“并不會被 Browserify 支持”,至少官方不會。Browserify 社區(qū)已經(jīng)找到了一些變通方案,這些方案可能會對已存在的復(fù)雜應(yīng)用有幫助。對于其它的場景,我們推薦直接使用 webpack,以擁有內(nèi)置的頭等異步支持。
處理加載狀態(tài)
2.3.0+ 新增
這里的異步組件工廠函數(shù)也可以返回一個(gè)如下格式的對象:
const AsyncComponent = () => ({
// 需要加載的組件 (應(yīng)該是一個(gè) `Promise` 對象)
component: import('./MyComponent.vue'),
// 異步組件加載時(shí)使用的組件
loading: LoadingComponent,
// 加載失敗時(shí)使用的組件
error: ErrorComponent,
// 展示加載時(shí)組件的延時(shí)時(shí)間。默認(rèn)值是 200 (毫秒)
delay: 200,
// 如果提供了超時(shí)時(shí)間且組件加載也超時(shí)了,
// 則使用加載失敗時(shí)使用的組件。默認(rèn)值是:`Infinity`
timeout: 3000
})
注意如果你希望在 Vue Router 的路由組件中使用上述語法的話,你必須使用 Vue Router 2.4.0+ 版本。
處理邊界情況
這里記錄的都是和處理邊界情況有關(guān)的功能,即一些需要對 Vue 的規(guī)則做一些小調(diào)整的特殊情況。不過注意這些功能都是有劣勢或危險(xiǎn)的場景的。我們會在每個(gè)案例中注明,所以當(dāng)你使用每個(gè)功能的時(shí)候請稍加留意。
訪問元素 & 組件
在絕大多數(shù)情況下,我們最好不要觸達(dá)另一個(gè)組件實(shí)例內(nèi)部或手動(dòng)操作 DOM 元素。不過也確實(shí)在一些情況下做這些事情是合適的。
訪問根實(shí)例
在每個(gè) new Vue 實(shí)例的子組件中,其根實(shí)例可以通過 $root 屬性進(jìn)行訪問。例如,在這個(gè)根實(shí)例中:
// Vue 根實(shí)例
new Vue({
data: {
foo: 1
},
computed: {
bar: function () { /* ... */ }
},
methods: {
baz: function () { /* ... */ }
}
})
所有的子組件都可以將這個(gè)實(shí)例作為一個(gè)全局 store 來訪問或使用。
// 獲取根組件的數(shù)據(jù)
this.$root.foo
// 寫入根組件的數(shù)據(jù)
this.$root.foo = 2
// 訪問根組件的計(jì)算屬性
this.$root.bar
// 調(diào)用根組件的方法
this.$root.baz()
未完待續(xù)