vue 初級

  

模板語法

Vue.js 使用了基於 HTML 的模板語法,容許開發者聲明式地將 DOM 綁定至底層 Vue 實例的數據。全部 Vue.js 的模板都是合法的 HTML ,因此能被遵循規範的瀏覽器和 HTML 解析器解析。javascript

在底層的實現上,Vue 將模板編譯成虛擬 DOM 渲染函數。結合響應系統,Vue 可以智能地計算出最少須要從新渲染多少組件,並把 DOM 操做次數減到最少。php

若是你熟悉虛擬 DOM 而且偏心 JavaScript 的原始力量,你也能夠不用模板,直接寫渲染 (render) 函數,使用可選的 JSX 語法。css

插值

文本

數據綁定最多見的形式就是使用「Mustache」語法 (雙大括號) 的文本插值:html

<span>Message: {{ msg }}</span>

Mustache 標籤將會被替代爲對應數據對象上 msg 屬性的值。不管什麼時候,綁定的數據對象上 msg 屬性發生了改變,插值處的內容都會更新。vue

經過使用 v-once 指令,你也能執行一次性地插值,當數據改變時,插值處的內容不會更新。但請留心這會影響到該節點上的其它數據綁定:java

<span v-once>這個將不會改變: {{ msg }}</span>

原始 HTML

雙大括號會將數據解釋爲普通文本,而非 HTML 代碼。爲了輸出真正的 HTML,你須要使用 v-html 指令:webpack

<p>Using mustaches: {{ rawHtml }}</p>
<p>Using v-html directive: <span v-html="rawHtml"></span></p>

Using mustaches: <span style="color: red">This should be red.</span>ios

Using v-html directive: This should be red.git

這個 span 的內容將會被替換成爲屬性值 rawHtml,直接做爲 HTML——會忽略解析屬性值中的數據綁定。注意,你不能使用 v-html 來複合局部模板,由於 Vue 不是基於字符串的模板引擎。反之,對於用戶界面 (UI),組件更適合做爲可重用和可組合的基本單位。github

你的站點上動態渲染的任意 HTML 可能會很是危險,由於它很容易致使 XSS 攻擊。請只對可信內容使用 HTML 插值,毫不要對用戶提供的內容使用插值。

特性

Mustache 語法不能做用在 HTML 特性上,遇到這種狀況應該使用 v-bind 指令

<div v-bind:id="dynamicId"></div>

在布爾特性的狀況下,它們的存在即暗示爲 truev-bind 工做起來略有不一樣,在這個例子中:

<button v-bind:disabled="isButtonDisabled">Button</button>

若是 isButtonDisabled 的值是 nullundefined 或 false,則 disabled 特性甚至不會被包含在渲染出來的 <button> 元素中。

使用 JavaScript 表達式

迄今爲止,在咱們的模板中,咱們一直都只綁定簡單的屬性鍵值。但實際上,對於全部的數據綁定,Vue.js 都提供了徹底的 JavaScript 表達式支持。

{{ number + 1 }}

{{ ok ? 'YES' : 'NO' }}

{{ message.split('').reverse().join('') }}

<div v-bind:id="'list-' + id"></div>

這些表達式會在所屬 Vue 實例的數據做用域下做爲 JavaScript 被解析。有個限制就是,每一個綁定都只能包含單個表達式,因此下面的例子都不會生效。

<!-- 這是語句,不是表達式 -->
{{ var a = 1 }}

<!-- 流控制也不會生效,請使用三元表達式 -->
{{ if (ok) { return message } }}

模板表達式都被放在沙盒中,只能訪問全局變量的一個白名單,如 Math 和 Date 。你不該該在模板表達式中試圖訪問用戶定義的全局變量。

指令

指令 (Directives) 是帶有 v- 前綴的特殊特性。指令特性的值預期是單個 JavaScript 表達式 (v-for 是例外狀況,稍後咱們再討論)。指令的職責是,當表達式的值改變時,將其產生的連帶影響,響應式地做用於 DOM。回顧咱們在介紹中看到的例子:

<p v-if="seen">如今你看到我了</p>

這裏,v-if 指令將根據表達式 seen 的值的真假來插入/移除 <p> 元素。

參數

一些指令可以接收一個「參數」,在指令名稱以後以冒號表示。例如,v-bind 指令能夠用於響應式地更新 HTML 特性:

<a v-bind:href="url">...</a>

在這裏 href 是參數,告知 v-bind 指令將該元素的 href 特性與表達式 url 的值綁定。

另外一個例子是 v-on 指令,它用於監聽 DOM 事件:

<a v-on:click="doSomething">...</a>

在這裏參數是監聽的事件名。咱們也會更詳細地討論事件處理。

修飾符

修飾符 (Modifiers) 是以半角句號 . 指明的特殊後綴,用於指出一個指令應該以特殊方式綁定。例如,.prevent 修飾符告訴 v-on 指令對於觸發的事件調用 event.preventDefault()

<form v-on:submit.prevent="onSubmit">...</form>

在接下來對 v-on 和 v-for 等功能的探索中,你會看到修飾符的其它例子。

縮寫

v- 前綴做爲一種視覺提示,用來識別模板中 Vue 特定的特性。當你在使用 Vue.js 爲現有標籤添加動態行爲 (dynamic behavior) 時,v- 前綴頗有幫助,然而,對於一些頻繁用到的指令來講,就會感到使用繁瑣。同時,在構建由 Vue.js 管理全部模板的單頁面應用程序 (SPA - single page application) 時,v- 前綴也變得沒那麼重要了。所以,Vue.js 爲 v-bind 和 v-on 這兩個最經常使用的指令,提供了特定簡寫:

v-bind 縮寫

<!-- 完整語法 -->
<a v-bind:href="url">...</a>

<!-- 縮寫 -->
<a :href="url">...</a>

v-on 縮寫

<!-- 完整語法 -->
<a v-on:click="doSomething">...</a>

<!-- 縮寫 -->
<a @click="doSomething">...</a>

它們看起來可能與普通的 HTML 略有不一樣,但 : 與 @ 對於特性名來講都是合法字符,在全部支持 Vue.js 的瀏覽器都能被正確地解析。並且,它們不會出如今最終渲染的標記中。縮寫語法是徹底可選的,但隨着你更深刻地瞭解它們的做用,你會慶幸擁有它們。

 

計算屬性和偵聽器

計算屬性

模板內的表達式很是便利,可是設計它們的初衷是用於簡單運算的。在模板中放入太多的邏輯會讓模板太重且難以維護。例如:

<div id="example">
{{ message.split('').reverse().join('') }}
</div>

在這個地方,模板再也不是簡單的聲明式邏輯。你必須看一段時間才能意識到,這裏是想要顯示變量 message 的翻轉字符串。當你想要在模板中屢次引用此處的翻轉字符串時,就會更加難以處理。

因此,對於任何複雜邏輯,你都應當使用計算屬性。

基礎例子

<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: {
// 計算屬性的 getter
reversedMessage: function () {
// `this` 指向 vm 實例
return this.message.split('').reverse().join('')
}
}
})

結果:

Original message: "Hello"

Computed reversed message: "olleH"

這裏咱們聲明瞭一個計算屬性 reversedMessage。咱們提供的函數將用做屬性 vm.reversedMessage 的 getter 函數:

console.log(vm.reversedMessage) // => 'olleH'
vm.message = 'Goodbye'
console.log(vm.reversedMessage) // => 'eybdooG'

你能夠打開瀏覽器的控制檯,自行修改例子中的 vm。vm.reversedMessage 的值始終取決於 vm.message 的值。

你能夠像綁定普通屬性同樣在模板中綁定計算屬性。Vue 知道 vm.reversedMessage依賴於 vm.message,所以當 vm.message 發生改變時,全部依賴 vm.reversedMessage 的綁定也會更新。並且最妙的是咱們已經以聲明的方式建立了這種依賴關係:計算屬性的 getter 函數是沒有反作用 (side effect) 的,這使它更易於測試和理解。

計算屬性緩存 vs 方法

你可能已經注意到咱們能夠經過在表達式中調用方法來達到一樣的效果:

<p>Reversed message: "{{ reversedMessage() }}"</p>
// 在組件中
methods: {
reversedMessage: function () {
return this.message.split('').reverse().join('')
}
}

咱們能夠將同一函數定義爲一個方法而不是一個計算屬性。兩種方式的最終結果確實是徹底相同的。然而,不一樣的是計算屬性是基於它們的依賴進行緩存的。只在相關依賴發生改變時它們纔會從新求值。這就意味着只要 message 尚未發生改變,屢次訪問 reversedMessage 計算屬性會當即返回以前的計算結果,而沒必要再次執行函數。

這也一樣意味着下面的計算屬性將再也不更新,由於 Date.now() 不是響應式依賴:

computed: {
now: function () {
return Date.now()
}
}

相比之下,每當觸發從新渲染時,調用方法將總會再次執行函數。

咱們爲何須要緩存?假設咱們有一個性能開銷比較大的計算屬性 A,它須要遍歷一個巨大的數組並作大量的計算。而後咱們可能有其餘的計算屬性依賴於 A 。若是沒有緩存,咱們將不可避免的屢次執行 A 的 getter!若是你不但願有緩存,請用方法來替代。

計算屬性 vs 偵聽屬性

Vue 提供了一種更通用的方式來觀察和響應 Vue 實例上的數據變更:偵聽屬性。當你有一些數據須要隨着其它數據變更而變更時,你很容易濫用 watch——特別是若是你以前使用過 AngularJS。然而,一般更好的作法是使用計算屬性而不是命令式的 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
}
}
})

上面代碼是命令式且重複的。將它與計算屬性的版本進行比較:

var vm = new Vue({
el: '#demo',
data: {
firstName: 'Foo',
lastName: 'Bar'
},
computed: {
fullName: function () {
return this.firstName + ' ' + this.lastName
}
}
})

好得多了,不是嗎?

計算屬性的 setter

計算屬性默認只有 getter ,不過在須要時你也能夠提供一個 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]
}
}
}
// ...

如今再運行 vm.fullName = 'John Doe' 時,setter 會被調用,vm.firstName 和 vm.lastName 也會相應地被更新。

偵聽器

雖然計算屬性在大多數狀況下更合適,但有時也須要一個自定義的偵聽器。這就是爲何 Vue 經過 watch 選項提供了一個更通用的方法,來響應數據的變化。當須要在數據變化時執行異步或開銷較大的操做時,這個方式是最有用的。

例如:

<div id="watch-example">
<p>
Ask a yes/no question:
<input v-model="question">
</p>
<p>{{ answer }}</p>
</div>
<!-- 由於 AJAX 庫和通用工具的生態已經至關豐富,Vue 核心代碼沒有重複 -->
<!-- 提供這些功能以保持精簡。這也可讓你自由選擇本身更熟悉的工具。 -->
<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` 發生改變,這個函數就會運行
question: function (newQuestion, oldQuestion) {
this.answer = 'Waiting for you to stop typing...'
this.debouncedGetAnswer()
}
},
created: function () {
  // `_.debounce` 是一個經過 Lodash 限制操做頻率的函數。
  // 在這個例子中,咱們但願限制訪問 yesno.wtf/api 的頻率
  // AJAX 請求直到用戶輸入完畢纔會發出。想要了解更多關於
// `_.debounce` 函數 (及其近親 `_.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>

結果:

Ask a yes/no question: 

I cannot give you an answer until you ask a question!

在這個示例中,使用 watch 選項容許咱們執行異步操做 (訪問一個 API),限制咱們執行該操做的頻率,並在咱們獲得最終結果前,設置中間狀態。這些都是計算屬性沒法作到的。

除了 watch 選項以外,您還可使用命令式的 vm.$watch API

 

Class 與 Style 綁定

操做元素的 class 列表和內聯樣式是數據綁定的一個常見需求。由於它們都是屬性,因此咱們能夠用 v-bind 處理它們:只須要經過表達式計算出字符串結果便可。不過,字符串拼接麻煩且易錯。所以,在將 v-bind 用於 class 和 style 時,Vue.js 作了專門的加強。表達式結果的類型除了字符串以外,還能夠是對象或數組。

綁定 HTML Class

對象語法

咱們能夠傳給 v-bind:class 一個對象,以動態地切換 class:

<div v-bind:class="{ active: isActive }"></div>

上面的語法表示 active 這個 class 存在與否將取決於數據屬性 isActive 的 truthiness

你能夠在對象中傳入更多屬性來動態切換多個 class。此外,v-bind:class 指令也能夠與普通的 class 屬性共存。當有以下模板:

<div class="static"
v-bind:class="{ active: isActive, 'text-danger': hasError }">
</div>

和以下 data:

data: {
isActive: true,
hasError: false
}

結果渲染爲:

<div class="static active"></div>

當 isActive 或者 hasError 變化時,class 列表將相應地更新。例如,若是 hasError 的值爲 true,class 列表將變爲 "static active text-danger"

綁定的數據對象沒必要內聯定義在模板裏:

<div v-bind:class="classObject"></div>
data: {
classObject: {
active: true,
'text-danger': false
}
}

渲染的結果和上面同樣。咱們也能夠在這裏綁定一個返回對象的計算屬性。這是一個經常使用且強大的模式:

<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'
}
}
}

數組語法

咱們能夠把一個數組傳給 v-bind:class,以應用一個 class 列表:

<div v-bind:class="[activeClass, errorClass]"></div>
data: {
activeClass: 'active',
errorClass: 'text-danger'
}

渲染爲:

<div class="active text-danger"></div>

若是你也想根據條件切換列表中的 class,能夠用三元表達式:

<div v-bind:class="[isActive ? activeClass : '', errorClass]"></div>

這樣寫將始終添加 errorClass,可是隻有在 isActive 是 truthy[1] 時才添加 activeClass

不過,當有多個條件 class 時這樣寫有些繁瑣。因此在數組語法中也可使用對象語法:

<div v-bind:class="[{ active: isActive }, errorClass]"></div>

用在組件上

這個章節假設你已經對 Vue 組件有必定的瞭解。固然你也能夠先跳過這裏,稍後再回過頭來看。

當在一個自定義組件上使用 class 屬性時,這些類將被添加到該組件的根元素上面。這個元素上已經存在的類不會被覆蓋。

例如,若是你聲明瞭這個組件:

Vue.component('my-component', {
template: '<p class="foo bar">Hi</p>'
})

而後在使用它的時候添加一些 class:

<my-component class="baz boo"></my-component>

HTML 將被渲染爲:

<p class="foo bar baz boo">Hi</p>

對於帶數據綁定 class 也一樣適用:

<my-component v-bind:class="{ active: isActive }"></my-component>

當 isActive 爲 truthy[1] 時,HTML 將被渲染成爲:

<p class="foo bar active">Hi</p>

綁定內聯樣式

對象語法

v-bind:style 的對象語法十分直觀——看着很是像 CSS,但實際上是一個 JavaScript 對象。CSS 屬性名能夠用駝峯式 (camelCase) 或短橫線分隔 (kebab-case,記得用單引號括起來) 來命名:

<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
data: {
activeColor: 'red',
fontSize: 30
}

直接綁定到一個樣式對象一般更好,這會讓模板更清晰:

<div v-bind:style="styleObject"></div>
data: {
styleObject: {
color: 'red',
fontSize: '13px'
}
}

一樣的,對象語法經常結合返回對象的計算屬性使用。

數組語法

v-bind:style 的數組語法能夠將多個樣式對象應用到同一個元素上:

<div v-bind:style="[baseStyles, overridingStyles]"></div>

自動添加前綴

當 v-bind:style 使用須要添加瀏覽器引擎前綴的 CSS 屬性時,如 transform,Vue.js 會自動偵測並添加相應的前綴。

多重值

2.3.0+

從 2.3.0 起你能夠爲 style 綁定中的屬性提供一個包含多個值的數組,經常使用於提供多個帶前綴的值,例如:

<div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div>

這樣寫只會渲染數組中最後一個被瀏覽器支持的值。在本例中,若是瀏覽器支持不帶瀏覽器前綴的 flexbox,那麼就只會渲染 display: flex


譯者注
[1] truthy 不是 true,詳見 MDN 的解釋。

 

v-if

在字符串模板中,好比 Handlebars,咱們得像這樣寫一個條件塊:

<!-- Handlebars 模板 -->
{{#if ok}}
<h1>Yes</h1>
{{/if}}

在 Vue 中,咱們使用 v-if 指令實現一樣的功能:

<h1 v-if="ok">Yes</h1>

也能夠用 v-else 添加一個「else 塊」:

<h1 v-if="ok">Yes</h1>
<h1 v-else>No</h1>

在 <template> 元素上使用 v-if 條件渲染分組

由於 v-if 是一個指令,因此必須將它添加到一個元素上。可是若是想切換多個元素呢?此時能夠把一個 <template> 元素當作不可見的包裹元素,並在上面使用 v-if。最終的渲染結果將不包含 <template> 元素。

<template v-if="ok">
<h1>Title</h1>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
</template>

v-else

你可使用 v-else 指令來表示 v-if 的「else 塊」:

<div v-if="Math.random() > 0.5">
Now you see me
</div>
<div v-else>
Now you don't
</div>

v-else 元素必須緊跟在帶 v-if 或者 v-else-if 的元素的後面,不然它將不會被識別。

v-else-if

2.1.0 新增

v-else-if,顧名思義,充當 v-if 的「else-if 塊」,能夠連續使用:

<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>

相似於 v-elsev-else-if 也必須緊跟在帶 v-if 或者 v-else-if 的元素以後。

用 key 管理可複用的元素

Vue 會盡量高效地渲染元素,一般會複用已有元素而不是從頭開始渲染。這麼作除了使 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 將不會清除用戶已經輸入的內容。由於兩個模板使用了相同的元素,<input> 不會被替換掉——僅僅是替換了它的 placeholder

本身動手試一試,在輸入框中輸入一些文本,而後按下切換按鈕:

 

這樣也不老是符合實際需求,因此 Vue 爲你提供了一種方式來表達「這兩個元素是徹底獨立的,不要複用它們」。只需添加一個具備惟一值的 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>

如今,每次切換時,輸入框都將被從新渲染。請看:

 

注意,<label> 元素仍然會被高效地複用,由於它們沒有添加 key 屬性。

v-show

另外一個用於根據條件展現元素的選項是 v-show 指令。用法大體同樣:

<h1 v-show="ok">Hello!</h1>

不一樣的是帶有 v-show 的元素始終會被渲染並保留在 DOM 中。v-show 只是簡單地切換元素的 CSS 屬性 display

注意,v-show 不支持 <template> 元素,也不支持 v-else

v-if vs v-show

v-if 是「真正」的條件渲染,由於它會確保在切換過程當中條件塊內的事件監聽器和子組件適當地被銷燬和重建。

v-if 也是惰性的:若是在初始渲染時條件爲假,則什麼也不作——直到條件第一次變爲真時,纔會開始渲染條件塊。

相比之下,v-show 就簡單得多——無論初始條件是什麼,元素老是會被渲染,而且只是簡單地基於 CSS 進行切換。

通常來講,v-if 有更高的切換開銷,而 v-show 有更高的初始渲染開銷。所以,若是須要很是頻繁地切換,則使用 v-show 較好;若是在運行時條件不多改變,則使用 v-if 較好。

v-if 與 v-for 一塊兒使用

不推薦同時使用 v-if 和 v-for。請查閱風格指南以獲取更多信息。

當 v-if 與 v-for 一塊兒使用時,v-for 具備比 v-if 更高的優先級。請查閱列表渲染指南 以獲取詳細信息。

 

列表渲染

用 v-for 把一個數組對應爲一組元素

咱們用 v-for 指令根據一組數組的選項列表進行渲染。v-for 指令須要使用 item in items 形式的特殊語法,items 是源數據數組而且 item 是數組元素迭代的別名。

<ul id="example-1">
<li v-for="item in items">
{{ item.message }}
</li>
</ul>
var example1 = new Vue({
el: '#example-1',
data: {
items: [
{ message: 'Foo' },
{ message: 'Bar' }
]
}
})

結果:

  • Foo
  • Bar

在 v-for 塊中,咱們擁有對父做用域屬性的徹底訪問權限。v-for 還支持一個可選的第二個參數爲當前項的索引。

<ul id="example-2">
<li v-for="(item, index) in items">
{{ parentMessage }} - {{ index }} - {{ item.message }}
</li>
</ul>
var example2 = new Vue({
el: '#example-2',
data: {
parentMessage: 'Parent',
items: [
{ message: 'Foo' },
{ message: 'Bar' }
]
}
})

結果:

  • Parent - 0 - Foo
  • Parent - 1 - Bar

你也能夠用 of 替代 in 做爲分隔符,由於它是最接近 JavaScript 迭代器的語法:

<div v-for="item of items"></div>

一個對象的 v-for

你也能夠用 v-for 經過一個對象的屬性來迭代。

<ul id="v-for-object" class="demo">
<li v-for="value in object">
{{ value }}
</li>
</ul>
new Vue({
el: '#v-for-object',
data: {
object: {
firstName: 'John',
lastName: 'Doe',
age: 30
}
}
})

結果:

  • John
  • Doe
  • 30

你也能夠提供第二個的參數爲鍵名:

<div v-for="(value, key) in object">
{{ key }}: {{ value }}
</div>
firstName: John
lastName: Doe
age: 30

第三個參數爲索引:

<div v-for="(value, key, index) in object">
{{ index }}. {{ key }}: {{ value }}
</div>
0. firstName: John
1. lastName: Doe
2. age: 30

在遍歷對象時,是按 Object.keys() 的結果遍歷,可是不能保證它的結果在不一樣的 JavaScript 引擎下是一致的。

key

當 Vue.js 用 v-for 正在更新已渲染過的元素列表時,它默認用「就地複用」策略。若是數據項的順序被改變,Vue 將不會移動 DOM 元素來匹配數據項的順序, 而是簡單複用此處每一個元素,而且確保它在特定索引下顯示已被渲染過的每一個元素。這個相似 Vue 1.x 的 track-by="$index" 。

這個默認的模式是高效的,可是隻適用於不依賴子組件狀態或臨時 DOM 狀態 (例如:表單輸入值) 的列表渲染輸出。

爲了給 Vue 一個提示,以便它能跟蹤每一個節點的身份,從而重用和從新排序現有元素,你須要爲每項提供一個惟一 key 屬性。理想的 key 值是每項都有的惟一 id。這個特殊的屬性至關於 Vue 1.x 的 track-by ,但它的工做方式相似於一個屬性,因此你須要用 v-bind 來綁定動態值 (在這裏使用簡寫):

<div v-for="item in items" :key="item.id">
<!-- 內容 -->
</div>

建議儘量在使用 v-for 時提供 key,除非遍歷輸出的 DOM 內容很是簡單,或者是刻意依賴默認行爲以獲取性能上的提高。

由於它是 Vue 識別節點的一個通用機制,key 並不與 v-for 特別關聯,key 還具備其餘用途,咱們將在後面的指南中看到其餘用途。

數組更新檢測

變異方法

Vue 包含一組觀察數組的變異方法,因此它們也將會觸發視圖更新。這些方法以下:

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()

你打開控制檯,而後用前面例子的 items 數組調用變異方法:example1.items.push({ message: 'Baz' }) 。

替換數組

變異方法 (mutation method),顧名思義,會改變被這些方法調用的原始數組。相比之下,也有非變異 (non-mutating method) 方法,例如:filter()concat() 和 slice() 。這些不會改變原始數組,但老是返回一個新數組。當使用非變異方法時,能夠用新數組替換舊數組:

example1.items = example1.items.filter(function (item) {
return item.message.match(/Foo/)
})

你可能認爲這將致使 Vue 丟棄現有 DOM 並從新渲染整個列表。幸運的是,事實並不是如此。Vue 爲了使得 DOM 元素獲得最大範圍的重用而實現了一些智能的、啓發式的方法,因此用一個含有相同元素的數組去替換原來的數組是很是高效的操做。

注意事項

因爲 JavaScript 的限制,Vue 不能檢測如下變更的數組:

  1. 當你利用索引直接設置一個項時,例如:vm.items[indexOfItem] = newValue
  2. 當你修改數組的長度時,例如:vm.items.length = newLength

舉個例子:

var vm = new Vue({
data: {
items: ['a', 'b', 'c']
}
})
vm.items[1] = 'x' // 不是響應性的
vm.items.length = 2 // 不是響應性的

爲了解決第一類問題,如下兩種方式均可以實現和 vm.items[indexOfItem] = newValue 相同的效果,同時也將觸發狀態更新:

// Vue.set
Vue.set(vm.items, indexOfItem, newValue)
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)

你也可使用 vm.$set 實例方法,該方法是全局方法 Vue.set 的一個別名:

vm.$set(vm.items, indexOfItem, newValue)

爲了解決第二類問題,你可使用 splice

vm.items.splice(newLength)

對象更改檢測注意事項

仍是因爲 JavaScript 的限制,Vue 不能檢測對象屬性的添加或刪除:

var vm = new Vue({
data: {
a: 1
}
})
// `vm.a` 如今是響應式的

vm.b = 2
// `vm.b` 不是響應式的

對於已經建立的實例,Vue 不能動態添加根級別的響應式屬性。可是,可使用 Vue.set(object, key, value) 方法向嵌套對象添加響應式屬性。例如,對於:

var vm = new Vue({
data: {
userProfile: {
name: 'Anika'
}
}
})

你能夠添加一個新的 age 屬性到嵌套的 userProfile 對象:

Vue.set(vm.userProfile, 'age', 27)

你還可使用 vm.$set 實例方法,它只是全局 Vue.set 的別名:

vm.$set(vm.userProfile, 'age', 27)

有時你可能須要爲已有對象賦予多個新屬性,好比使用 Object.assign() 或 _.extend()。在這種狀況下,你應該用兩個對象的屬性建立一個新的對象。因此,若是你想添加新的響應式屬性,不要像這樣:

Object.assign(vm.userProfile, {
age: 27,
favoriteColor: 'Vue Green'
})

你應該這樣作:

vm.userProfile = Object.assign({}, vm.userProfile, {
age: 27,
favoriteColor: 'Vue Green'
})

顯示過濾/排序結果

有時,咱們想要顯示一個數組的過濾或排序副本,而不實際改變或重置原始數據。在這種狀況下,能夠建立返回過濾或排序數組的計算屬性。

例如:

<li v-for="n in evenNumbers">{{ n }}</li>
data: {
numbers: [ 1, 2, 3, 4, 5 ]
},
computed: {
evenNumbers: function () {
return this.numbers.filter(function (number) {
return number % 2 === 0
})
}
}

在計算屬性不適用的狀況下 (例如,在嵌套 v-for 循環中) 你可使用一個 method 方法:

<li v-for="n in even(numbers)">{{ n }}</li>
data: {
numbers: [ 1, 2, 3, 4, 5 ]
},
methods: {
even: function (numbers) {
return numbers.filter(function (number) {
return number % 2 === 0
})
}
}

一段取值範圍的 v-for

v-for 也能夠取整數。在這種狀況下,它將重複屢次模板。

<div>
<span v-for="n in 10">{{ n }} </span>
</div>

結果:

1 2 3 4 5 6 7 8 9 10

v-for on a <template>

相似於 v-if,你也能夠利用帶有 v-for 的 <template> 渲染多個元素。好比:

<ul>
<template v-for="item in items">
<li>{{ item.msg }}</li>
<li class="divider" role="presentation"></li>
</template>
</ul>

v-for with v-if

當它們處於同一節點,v-for 的優先級比 v-if 更高,這意味着 v-if 將分別重複運行於每一個 v-for 循環中。當你想爲僅有的一些項渲染節點時,這種優先級的機制會十分有用,以下:

<li v-for="todo in todos" v-if="!todo.isComplete">
{{ todo }}
</li>

上面的代碼只傳遞了未完成的 todos。

而若是你的目的是有條件地跳過循環的執行,那麼能夠將 v-if 置於外層元素 (或 <template>)上。如:

<ul v-if="todos.length">
<li v-for="todo in todos">
{{ todo }}
</li>
</ul>
<p v-else>No todos left!</p>

一個組件的 v-for

瞭解組件相關知識,查看 組件。徹底能夠先跳過它,之後再回來查看。

在自定義組件裏,你能夠像任何普通元素同樣用 v-for 。

<my-component v-for="item in items" :key="item.id"></my-component>

2.2.0+ 的版本里,當在組件中使用 v-for 時,key 如今是必須的。

然而,任何數據都不會被自動傳遞到組件裏,由於組件有本身獨立的做用域。爲了把迭代數據傳遞到組件裏,咱們要用 props :

<my-component
v-for="(item, index) in items"
v-bind:item="item"
v-bind:index="index"
v-bind:key="item.id"
></my-component>

不自動將 item 注入到組件裏的緣由是,這會使得組件與 v-for 的運做緊密耦合。明確組件數據的來源可以使組件在其餘場合重複使用。

下面是一個簡單的 todo list 的完整例子:

<div id="todo-list-example">
<form v-on:submit.prevent="addNewTodo">
<label for="new-todo">Add a todo</label>
<input
v-model="newTodoText"
id="new-todo"
placeholder="E.g. Feed the cat"
>
<button>Add</button>
</form>
<ul>
<li
is="todo-item"
v-for="(todo, index) in todos"
v-bind:key="todo.id"
v-bind:title="todo.title"
v-on:remove="todos.splice(index, 1)"
></li>
</ul>
</div>

注意這裏的 is="todo-item" 屬性。這種作法在使用 DOM 模板時是十分必要的,由於在 <ul> 元素內只有 <li> 元素會被看做有效內容。這樣作實現的效果與 <todo-item> 相同,可是能夠避開一些潛在的瀏覽器解析錯誤。查看 DOM 模板解析說明 來了解更多信息。

Vue.component('todo-item', {
template: '\
<li>\
{{ title }}\
<button v-on:click="$emit(\'remove\')">Remove</button>\
</li>\
',
props: ['title']
})

new Vue({
el: '#todo-list-example',
data: {
newTodoText: '',
todos: [
{
id: 1,
title: 'Do the dishes',
},
{
id: 2,
title: 'Take out the trash',
},
{
id: 3,
title: 'Mow the lawn'
}
],
nextTodoId: 4
},
methods: {
addNewTodo: function () {
this.todos.push({
id: this.nextTodoId++,
title: this.newTodoText
})
this.newTodoText = ''
}
}
})
   
  • Do the dishes 
  • Take out the trash 
  • Mow the lawn 

 

事件處理

監聽事件

能夠用 v-on 指令監聽 DOM 事件,並在觸發時運行一些 JavaScript 代碼。

示例:

<div id="example-1">
<button v-on:click="counter += 1">Add 1</button>
<p>The button above has been clicked {{ counter }} times.</p>
</div>
var example1 = new Vue({
el: '#example-1',
data: {
counter: 0
}
})

結果:

The button above has been clicked 0 times.

事件處理方法

然而許多事件處理邏輯會更爲複雜,因此直接把 JavaScript 代碼寫在 v-on 指令中是不可行的。所以 v-on 還能夠接收一個須要調用的方法名稱。

示例:

<div id="example-2">
<!-- `greet` 是在下面定義的方法名 -->
<button v-on:click="greet">Greet</button>
</div>
var example2 = new Vue({
el: '#example-2',
data: {
name: 'Vue.js'
},
// 在 `methods` 對象中定義方法
methods: {
greet: function (event) {
// `this` 在方法裏指向當前 Vue 實例
alert('Hello ' + this.name + '!')
// `event` 是原生 DOM 事件
if (event) {
alert(event.target.tagName)
}
}
}
})

// 也能夠用 JavaScript 直接調用方法
example2.greet() // => 'Hello Vue.js!'

結果:

內聯處理器中的方法

除了直接綁定到一個方法,也能夠在內聯 JavaScript 語句中調用方法:

<div id="example-3">
<button v-on:click="say('hi')">Say hi</button>
<button v-on:click="say('what')">Say what</button>
</div>
new Vue({
el: '#example-3',
methods: {
say: function (message) {
alert(message)
}
}
})

結果:

 

有時也須要在內聯語句處理器中訪問原始的 DOM 事件。能夠用特殊變量 $event 把它傳入方法:

<button v-on:click="warn('Form cannot be submitted yet.', $event)">
Submit
</button>
// ...
methods: {
warn: function (message, event) {
// 如今咱們能夠訪問原生事件對象
if (event) event.preventDefault()
alert(message)
}
}

事件修飾符

在事件處理程序中調用 event.preventDefault() 或 event.stopPropagation() 是很是常見的需求。儘管咱們能夠在方法中輕鬆實現這點,但更好的方式是:方法只有純粹的數據邏輯,而不是去處理 DOM 事件細節。

爲了解決這個問題,Vue.js 爲 v-on 提供了事件修飾符。以前提過,修飾符是由點開頭的指令後綴來表示的。

  • .stop
  • .prevent
  • .capture
  • .self
  • .once
  • .passive
<!-- 阻止單擊事件繼續傳播 -->
<a v-on:click.stop="doThis"></a>

<!-- 提交事件再也不重載頁面 -->
<form v-on:submit.prevent="onSubmit"></form>

<!-- 修飾符能夠串聯 -->
<a v-on:click.stop.prevent="doThat"></a>

<!-- 只有修飾符 -->
<form v-on:submit.prevent></form>

<!-- 添加事件監聽器時使用事件捕獲模式 -->
<!-- 即元素自身觸發的事件先在此處理,而後才交由內部元素進行處理 -->
<div v-on:click.capture="doThis">...</div>

<!-- 只當在 event.target 是當前元素自身時觸發處理函數 -->
<!-- 即事件不是從內部元素觸發的 -->
<div v-on:click.self="doThat">...</div>

使用修飾符時,順序很重要;相應的代碼會以一樣的順序產生。所以,用 v-on:click.prevent.self 會阻止全部的點擊,而 v-on:click.self.prevent 只會阻止對元素自身的點擊。

2.1.4 新增

<!-- 點擊事件將只會觸發一次 -->
<a v-on:click.once="doThis"></a>

不像其它只能對原生的 DOM 事件起做用的修飾符,.once 修飾符還能被用到自定義的組件事件上。若是你尚未閱讀關於組件的文檔,如今大可沒必要擔憂。

2.3.0 新增

Vue 還對應 addEventListener 中的 passive 選項提供了 .passive 修飾符。

<!-- 滾動事件的默認行爲 (即滾動行爲) 將會當即觸發 -->
<!-- 而不會等待 `onScroll` 完成 -->
<!-- 這其中包含 `event.preventDefault()` 的狀況 -->
<div v-on:scroll.passive="onScroll">...</div>

這個 .passive 修飾符尤爲可以提高移動端的性能。

不要把 .passive 和 .prevent 一塊兒使用,由於 .prevent 將會被忽略,同時瀏覽器可能會向你展現一個警告。請記住,.passive 會告訴瀏覽器你想阻止事件的默認行爲。

按鍵修飾符

在監聽鍵盤事件時,咱們常常須要檢查常見的鍵值。Vue 容許爲 v-on 在監聽鍵盤事件時添加按鍵修飾符:

<!-- 只有在 `keyCode` 是 13 時調用 `vm.submit()` -->
<input v-on:keyup.13="submit">

記住全部的 keyCode 比較困難,因此 Vue 爲最經常使用的按鍵提供了別名:

<!-- 同上 -->
<input v-on:keyup.enter="submit">

<!-- 縮寫語法 -->
<input @keyup.enter="submit">

所有的按鍵別名:

  • .enter
  • .tab
  • .delete (捕獲「刪除」和「退格」鍵)
  • .esc
  • .space
  • .up
  • .down
  • .left
  • .right

能夠經過全局 config.keyCodes 對象自定義按鍵修飾符別名

// 可使用 `v-on:keyup.f1`
Vue.config.keyCodes.f1 = 112

自動匹配按鍵修飾符

2.5.0 新增

你也可直接將 KeyboardEvent.key 暴露的任意有效按鍵名轉換爲 kebab-case 來做爲修飾符:

<input @keyup.page-down="onPageDown">

在上面的例子中,處理函數僅在 $event.key === 'PageDown' 時被調用。

有一些按鍵 (.esc 以及全部的方向鍵) 在 IE9 中有不一樣的 key 值, 若是你想支持 IE9,它們的內置別名應該是首選。

系統修飾鍵

2.1.0 新增

能夠用以下修飾符來實現僅在按下相應按鍵時才觸發鼠標或鍵盤事件的監聽器。

  • .ctrl
  • .alt
  • .shift
  • .meta

注意:在 Mac 系統鍵盤上,meta 對應 command 鍵 (⌘)。在 Windows 系統鍵盤 meta 對應 Windows 徽標鍵 (⊞)。在 Sun 操做系統鍵盤上,meta 對應實心寶石鍵 (◆)。在其餘特定鍵盤上,尤爲在 MIT 和 Lisp 機器的鍵盤、以及其後繼產品,好比 Knight 鍵盤、space-cadet 鍵盤,meta 被標記爲「META」。在 Symbolics 鍵盤上,meta 被標記爲「META」或者「Meta」。

例如:

<!-- Alt + C -->
<input @keyup.alt.67="clear">

<!-- Ctrl + Click -->
<div @click.ctrl="doSomething">Do something</div>

請注意修飾鍵與常規按鍵不一樣,在和 keyup 事件一塊兒用時,事件觸發時修飾鍵必須處於按下狀態。換句話說,只有在按住 ctrl 的狀況下釋放其它按鍵,才能觸發 keyup.ctrl。而單單釋放 ctrl 也不會觸發事件。若是你想要這樣的行爲,請爲 ctrl 換用 keyCodekeyup.17

.exact 修飾符

2.5.0 新增

.exact 修飾符容許你控制由精確的系統修飾符組合觸發的事件。

<!-- 即便 Alt 或 Shift 被一同按下時也會觸發 -->
<button @click.ctrl="onClick">A</button>

<!-- 有且只有 Ctrl 被按下的時候才觸發 -->
<button @click.ctrl.exact="onCtrlClick">A</button>

<!-- 沒有任何系統修飾符被按下的時候才觸發 -->
<button @click.exact="onClick">A</button>

鼠標按鈕修飾符

2.2.0 新增

  • .left
  • .right
  • .middle

這些修飾符會限制處理函數僅響應特定的鼠標按鈕。

爲何在 HTML 中監聽事件?

你可能注意到這種事件監聽的方式違背了關注點分離 (separation of concern) 這個長期以來的優良傳統。但沒必要擔憂,由於全部的 Vue.js 事件處理方法和表達式都嚴格綁定在當前視圖的 ViewModel 上,它不會致使任何維護上的困難。實際上,使用 v-on 有幾個好處:

  1. 掃一眼 HTML 模板便能輕鬆定位在 JavaScript 代碼裏對應的方法。

  2. 由於你無須在 JavaScript 裏手動綁定事件,你的 ViewModel 代碼能夠是很是純粹的邏輯,和 DOM 徹底解耦,更易於測試。

  3. 當一個 ViewModel 被銷燬時,全部的事件處理器都會自動被刪除。你無須擔憂如何清理它們。

 

表單輸入綁定

基礎用法

你能夠用 v-model 指令在表單 <input><textarea> 及 <select> 元素上建立雙向數據綁定。它會根據控件類型自動選取正確的方法來更新元素。儘管有些神奇,但 v-model 本質上不過是語法糖。它負責監聽用戶的輸入事件以更新數據,並對一些極端場景進行一些特殊處理。

v-model 會忽略全部表單元素的 valuecheckedselected 特性的初始值而老是將 Vue 實例的數據做爲數據來源。你應該經過 JavaScript 在組件的 data 選項中聲明初始值。

對於須要使用輸入法 (如中文、日文、韓文等) 的語言,你會發現 v-model 不會在輸入法組合文字過程當中獲得更新。若是你也想處理這個過程,請使用 input 事件。

文本

<input v-model="message" placeholder="edit me">
<p>Message is: {{ message }}</p>

Message is:

多行文本

<span>Multiline message is:</span>
<p style="white-space: pre-line;">{{ message }}</p>
<br>
<textarea v-model="message" placeholder="add multiple lines"></textarea>
Multiline message is:

 


在文本區域插值 (<textarea></textarea>) 並不會生效,應用 v-model 來代替。

複選框

單個複選框,綁定到布爾值:

<input type="checkbox" id="checkbox" v-model="checked">
<label for="checkbox">{{ checked }}</label>
 

多個複選框,綁定到同一個數組:

<div id='example-3'>
<input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
<label for="jack">Jack</label>
<input type="checkbox" id="john" value="John" v-model="checkedNames">
<label for="john">John</label>
<input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
<label for="mike">Mike</label>
<br>
<span>Checked names: {{ checkedNames }}</span>
</div>
new Vue({
el: '#example-3',
data: {
checkedNames: []
}
})
           
Checked names: []

單選按鈕

<div id="example-4">
<input type="radio" id="one" value="One" v-model="picked">
<label for="one">One</label>
<br>
<input type="radio" id="two" value="Two" v-model="picked">
<label for="two">Two</label>
<br>
<span>Picked: {{ picked }}</span>
</div>
new Vue({
el: '#example-4',
data: {
picked: ''
}
})
   
   
Picked:

選擇框

單選時:

<div id="example-5">
<select v-model="selected">
<option disabled value="">請選擇</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<span>Selected: {{ selected }}</span>
</div>
new Vue({
el: '...',
data: {
selected: ''
}
})
 Selected:

若是 v-model 表達式的初始值未能匹配任何選項,<select> 元素將被渲染爲「未選中」狀態。在 iOS 中,這會使用戶沒法選擇第一個選項。由於這樣的狀況下,iOS 不會觸發 change 事件。所以,更推薦像上面這樣提供一個值爲空的禁用選項。

多選時 (綁定到一個數組):

<div id="example-6">
<select v-model="selected" multiple style="width: 50px;">
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<br>
<span>Selected: {{ selected }}</span>
</div>
new Vue({
el: '#example-6',
data: {
selected: []
}
})
 
Selected: []

用 v-for 渲染的動態選項:

<select v-model="selected">
<option v-for="option in options" v-bind:value="option.value">
{{ option.text }}
</option>
</select>
<span>Selected: {{ selected }}</span>
new Vue({
el: '...',
data: {
selected: 'A',
options: [
{ text: 'One', value: 'A' },
{ text: 'Two', value: 'B' },
{ text: 'Three', value: 'C' }
]
}
})
 Selected: A

值綁定

對於單選按鈕,複選框及選擇框的選項,v-model 綁定的值一般是靜態字符串 (對於複選框也能夠是布爾值):

<!-- 當選中時,`picked` 爲字符串 "a" -->
<input type="radio" v-model="picked" value="a">

<!-- `toggle` 爲 true 或 false -->
<input type="checkbox" v-model="toggle">

<!-- 當選中第一個選項時,`selected` 爲字符串 "abc" -->
<select v-model="selected">
<option value="abc">ABC</option>
</select>

可是有時咱們可能想把值綁定到 Vue 實例的一個動態屬性上,這時能夠用 v-bind 實現,而且這個屬性的值能夠不是字符串。

複選框

<input
type="checkbox"
v-model="toggle"
true-value="yes"
false-value="no"
>
// 當選中時
vm.toggle === 'yes'
// 當沒有選中時
vm.toggle === 'no'

這裏的 true-value 和 false-value 特性並不會影響輸入控件的 value 特性,由於瀏覽器在提交表單時並不會包含未被選中的複選框。若是要確保表單中這兩個值中的一個可以被提交,(好比「yes」或「no」),請換用單選按鈕。

單選按鈕

<input type="radio" v-model="pick" v-bind:value="a">
// 當選中時
vm.pick === vm.a

選擇框的選項

<select v-model="selected">
<!-- 內聯對象字面量 -->
<option v-bind:value="{ number: 123 }">123</option>
</select>
// 當選中時
typeof vm.selected // => 'object'
vm.selected.number // => 123

修飾符

.lazy

在默認狀況下,v-model 在每次 input 事件觸發後將輸入框的值與數據進行同步 (除了上述輸入法組合文字時)。你能夠添加 lazy 修飾符,從而轉變爲使用 change事件進行同步:

<!-- 在「change」時而非「input」時更新 -->
<input v-model.lazy="msg" >

.number

若是想自動將用戶的輸入值轉爲數值類型,能夠給 v-model 添加 number 修飾符:

<input v-model.number="age" type="number">

這一般頗有用,由於即便在 type="number" 時,HTML 輸入元素的值也總會返回字符串。若是這個值沒法被 parseFloat() 解析,則會返回原始的值。

.trim

若是要自動過濾用戶輸入的首尾空白字符,能夠給 v-model 添加 trim 修飾符:

<input v-model.trim="msg">

在組件上使用 v-model

若是你還不熟悉 Vue 的組件,能夠暫且跳過這裏。

HTML 原生的輸入元素類型並不總能知足需求。幸虧,Vue 的組件系統容許你建立具備徹底自定義行爲且可複用的輸入組件。這些輸入組件甚至能夠和 v-model 一塊兒使用!要了解更多,請參閱組件指南中的自定義輸入組件

 

組件基礎

基本示例

這裏有一個 Vue 組件的示例:

// 定義一個名爲 button-counter 的新組件
Vue.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})

組件是可複用的 Vue 實例,且帶有一個名字:在這個例子中是 <button-counter>。咱們能夠在一個經過 new Vue 建立的 Vue 根實例中,把這個組件做爲自定義元素來使用:

<div id="components-demo">
<button-counter></button-counter>
</div>
new Vue({ el: '#components-demo' })

由於組件是可複用的 Vue 實例,因此它們與 new Vue 接收相同的選項,例如 datacomputedwatchmethods 以及生命週期鉤子等。僅有的例外是像 el這樣根實例特有的選項。

組件的複用

你能夠將組件進行任意次數的複用:

<div id="components-demo">
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
</div>
   

注意當點擊按鈕時,每一個組件都會各自獨立維護它的 count。由於你每用一次組件,就會有一個它的新實例被建立。

data 必須是一個函數

當咱們定義這個 <button-counter> 組件時,你可能會發現它的 data 並非像這樣直接提供一個對象:

data: {
count: 0
}

取而代之的是,一個組件的 data 選項必須是一個函數,所以每一個實例能夠維護一份被返回對象的獨立的拷貝:

data: function () {
return {
count: 0
}
}

若是 Vue 沒有這條規則,點擊一個按鈕就可能會像以下代碼同樣影響到其它全部實例

   

組件的組織

一般一個應用會以一棵嵌套的組件樹的形式來組織:

Component Tree

例如,你可能會有頁頭、側邊欄、內容區等組件,每一個組件又包含了其它的像導航連接、博文之類的組件。

爲了能在模板中使用,這些組件必須先註冊以便 Vue 可以識別。這裏有兩種組件的註冊類型:全局註冊和局部註冊。至此,咱們的組件都只是經過 Vue.component 全局註冊的:

Vue.component('my-component-name', {
// ... options ...
})

全局註冊的組件能夠用在其被註冊以後的任何 (經過 new Vue) 新建立的 Vue 根實例,也包括其組件樹中的全部子組件的模板中。

到目前爲止,關於組件註冊你須要瞭解的就這些了,若是你閱讀完本頁內容並掌握了它的內容,咱們會推薦你再回來把組件註冊讀完。

經過 Prop 向子組件傳遞數據

早些時候,咱們提到了建立一個博文組件的事情。問題是若是你不能向這個組件傳遞某一篇博文的標題或內容之類的咱們想展現的數據的話,它是沒有辦法使用的。這也正是 prop 的由來。

Prop 是你能夠在組件上註冊的一些自定義特性。當一個值傳遞給一個 prop 特性的時候,它就變成了那個組件實例的一個屬性。爲了給博文組件傳遞一個標題,咱們能夠用一個 props 選項將其包含在該組件可接受的 prop 列表中:

Vue.component('blog-post', {
props: ['title'],
template: '<h3>{{ title }}</h3>'
})

一個組件默承認以擁有任意數量的 prop,任何值均可以傳遞給任何 prop。在上述模板中,你會發現咱們可以在組件實例中訪問這個值,就像訪問 data 中的值同樣。

一個 prop 被註冊以後,你就能夠像這樣把數據做爲一個自定義特性傳遞進來:

<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>

My journey with Vue

Blogging with Vue

Why Vue is so fun

然而在一個典型的應用中,你可能在 data 裏有一個博文的數組:

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>

如上所示,你會發現咱們可使用 v-bind 來動態傳遞 prop。這在你一開始不清楚要渲染的具體內容,好比從一個 API 獲取博文列表的時候,是很是有用的。

到目前爲止,關於 prop 你須要瞭解的大概就這些了,若是你閱讀完本頁內容並掌握了它的內容,咱們會推薦你再回來把 prop 讀完。

單個根元素

當構建一個 <blog-post> 組件時,你的模板最終會包含的東西遠不止一個標題:

<h3>{{ title }}</h3>

最最起碼,你會包含這篇博文的正文:

<h3>{{ title }}</h3>
<div v-html="content"></div>

然而若是你在模板中嘗試這樣寫,Vue 會顯示一個錯誤,並解釋道 every component must have a single root element (每一個組件必須只有一個根元素)。你能夠將模板的內容包裹在一個父元素內,來修復這個問題,例如:

<div class="blog-post">
<h3>{{ title }}</h3>
<div v-html="content"></div>
</div>

看起來當組件變得愈來愈複雜的時候,咱們的博文不僅須要標題和內容,還須要發佈日期、評論等等。爲每一個相關的信息定義一個 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>

因此是時候重構一下這個 <blog-post> 組件了,讓它變成接受一個單獨的 postprop:

<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>
`
})

上述的這個和一些接下來的示例使用了 JavaScript 的模板字符串來讓多行的模板更易讀。它們在 IE 下並無被支持,因此若是你須要在不 (通過 Babel 或 TypeScript 之類的工具) 編譯的狀況下支持 IE,請使用折行轉義字符取而代之。

如今,不論什麼時候爲 post 對象添加一個新的屬性,它都會自動地在 <blog-post> 內可用。

經過事件向父級組件發送消息

在咱們開發 <blog-post> 組件時,它的一些功能可能要求咱們和父級組件進行溝通。例如咱們可能會引入一個可訪問性的功能來放大博文的字號,同時讓頁面的其它部分保持默認的字號。

在其父組件中,咱們能夠經過添加一個 postFontSize 數據屬性來支持這個功能:

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>

如今咱們在每篇博文正文以前添加一個按鈕來放大字號:

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>
`
})

問題是這個按鈕不會作任何事:

<button>
Enlarge text
</button>

當點擊這個按鈕時,咱們須要告訴父級組件放大全部博文的文本。幸虧 Vue 實例提供了一個自定義事件的系統來解決這個問題。咱們能夠調用內建的 $emit 方法並傳入事件的名字,來向父級組件觸發一個事件:

<button v-on:click="$emit('enlarge-text')">
Enlarge text
</button>

而後咱們能夠用 v-on 在博文組件上監聽這個事件,就像監聽一個原生 DOM 事件同樣:

<blog-post
...
v-on:enlarge-text="postFontSize += 0.1"
></blog-post>

My journey with Vue

...content...

Blogging with Vue

...content...

Why Vue is so fun

...content...

使用事件拋出一個值

有的時候用一個事件來拋出一個特定的值是很是有用的。例如咱們可能想讓 <blog-post> 組件決定它的文本要放大多少。這時可使用 $emit 的第二個參數來提供這個值:

<button v-on:click="$emit('enlarge-text', 0.1)">
Enlarge text
</button>

而後當在父級組件監聽這個事件的時候,咱們能夠經過 $event 訪問到被拋出的這個值:

<blog-post
...
v-on:enlarge-text="postFontSize += $event"
></blog-post>

或者,若是這個事件處理函數是一個方法:

<blog-post
...
v-on:enlarge-text="onEnlargeText"
></blog-post>

那麼這個值將會做爲第一個參數傳入這個方法:

methods: {
onEnlargeText: function (enlargeAmount) {
this.postFontSize += enlargeAmount
}
}

在組件上使用 v-model

自定義事件也能夠用於建立支持 v-model 的自定義輸入組件。記住:

<input v-model="searchText">

等價於:

<input
v-bind:value="searchText"
v-on:input="searchText = $event.target.value"
>

當用在組件上時,v-model 則會這樣:

<custom-input
v-bind:value="searchText"
v-on:input="searchText = $event"
></custom-input>

爲了讓它正常工做,這個組件內的 <input> 必須:

  • 將其 value 特性綁定到一個名叫 value 的 prop 上
  • 在其 input 事件被觸發時,將新的值經過自定義的 input 事件拋出

寫成代碼以後是這樣的:

Vue.component('custom-input', {
props: ['value'],
template: `
<input
v-bind:value="value"
     v-on:input="$emit('input', $event.target.value)"
   >
`
})

如今 v-model 就應該能夠在這個組件上完美地工做起來了:

<custom-input v-model="searchText"></custom-input>

到目前爲止,關於組件自定義事件你須要瞭解的大概就這些了,若是你閱讀完本頁內容並掌握了它的內容,咱們會推薦你再回來把自定義事件讀完。

經過插槽分發內容

和 HTML 元素同樣,咱們常常須要向一個組件傳遞內容,像這樣:

<alert-box>
Something bad happened.
</alert-box>

可能會渲染出這樣的東西:

Error! Something bad happened.

幸虧,Vue 自定義的 <slot> 元素讓這變得很是簡單:

Vue.component('alert-box', {
template: `
<div class="demo-alert-box">
<strong>Error!</strong>
<slot></slot>
</div>
`
})

如你所見,咱們只要在須要的地方加入插槽就好了——就這麼簡單!

到目前爲止,關於插槽你須要瞭解的大概就這些了,若是你閱讀完本頁內容並掌握了它的內容,咱們會推薦你再回來把插槽讀完。

動態組件

有的時候,在不一樣組件之間進行動態切換是很是有用的,好比在一個多標籤的界面裏:

Home component

上述內容能夠經過 Vue 的 <component> 元素加一個特殊的 is 特性來實現:

<!-- 組件會在 `currentTabComponent` 改變時改變 -->
<component v-bind:is="currentTabComponent"></component>

在上述示例中,currentTabComponent 能夠包括

  • 已註冊組件的名字,或
  • 一個組件的選項對象

你能夠在這裏查閱並體驗完整的代碼,或在這個版本瞭解綁定組件選項對象,而不是已註冊組件名的示例。

到目前爲止,關於動態組件你須要瞭解的大概就這些了,若是你閱讀完本頁內容並掌握了它的內容,咱們會推薦你再回來把動態和異步組件讀完。

解析 DOM 模板時的注意事項

有些 HTML 元素,諸如 <ul><ol><table> 和 <select>,對於哪些元素能夠出如今其內部是有嚴格限制的。而有些元素,諸如 <li><tr> 和 <option>,只能出如今其它某些特定的元素內部。

這會致使咱們使用這些有約束條件的元素時遇到一些問題。例如:

<table>
<blog-post-row></blog-post-row>
</table>

這個自定義組件 <blog-post-row> 會被做爲無效的內容提高到外部,並致使最終渲染結果出錯。幸虧這個特殊的 is 特性給了咱們一個變通的辦法:

<table>
<tr is="blog-post-row"></tr>
</table>

須要注意的是若是咱們從如下來源使用模板的話,這條限制是不存在的:

到這裏,你須要瞭解的解析 DOM 模板時的注意事項——實際上也是 Vue 的所有必要內容,大概就是這些了。恭喜你!接下來還有不少東西要去學習,不過首先,咱們推薦你先休息一下,試用一下 Vue,本身隨意作些好玩的東西。

若是你感受已經掌握了這些知識,咱們推薦你再回來把完整的組件指南,包括側邊欄中組件深刻章節的全部頁面讀完。

 

組件註冊

該頁面假設你已經閱讀過了組件基礎。若是你還對組件不太瞭解,推薦你先閱讀它。

組件名

在註冊一個組件的時候,咱們始終須要給它一個名字。好比在全局註冊的時候咱們已經看到了:

Vue.component('my-component-name', { /* ... */ })

該組件名就是 Vue.component 的第一個參數。

你給予組件的名字可能依賴於你打算拿它來作什麼。當直接在 DOM 中使用一個組件 (而不是在字符串模板或單文件組件) 的時候,咱們強烈推薦遵循 W3C 規範中的自定義組件名 (字母全小寫且必須包含一個連字符)。這會幫助你避免和當前以及將來的 HTML 元素相沖突。

你能夠在風格指南中查閱到關於組件名的其它建議。

組件名大小寫

定義組件名的方式有兩種:

使用 kebab-case

Vue.component('my-component-name', { /* ... */ })

當使用 kebab-case (短橫線分隔命名) 定義一個組件時,你也必須在引用這個自定義元素時使用 kebab-case,例如 <my-component-name>

使用 PascalCase

Vue.component('MyComponentName', { /* ... */ })

當使用 PascalCase (首字母大寫命名) 定義一個組件時,你在引用這個自定義元素時兩種命名法均可以使用。也就是說 <my-component-name> 和 <MyComponentName> 都是可接受的。注意,儘管如此,直接在 DOM (即非字符串的模板) 中使用時只有 kebab-case 是有效的。

全局註冊

到目前爲止,咱們只用過 Vue.component 來建立組件:

Vue.component('my-component-name', {
 // ... 選項 ...
})

這些組件是全局註冊的。也就是說它們在註冊以後能夠用在任何新建立的 Vue 根實例 (new Vue) 的模板中。好比:

Vue.component('component-a', { /* ... */ })
Vue.component('component-b', { /* ... */ })
Vue.component('component-c', { /* ... */ })

new Vue({ el: '#app' })
<div id="app">
<component-a></component-a>
<component-b></component-b>
<component-c></component-c>
</div>

在全部子組件中也是如此,也就是說這三個組件在各自內部也均可以相互使用。

局部註冊

全局註冊每每是不夠理想的。好比,若是你使用一個像 webpack 這樣的構建系統,全局註冊全部的組件意味着即使你已經再也不使用一個組件了,它仍然會被包含在你最終的構建結果中。這形成了用戶下載的 JavaScript 的無謂的增長。

在這些狀況下,你能夠經過一個普通的 JavaScript 對象來定義組件:

var ComponentA = { /* ... */ }
var ComponentB = { /* ... */ }
var ComponentC = { /* ... */ }

而後在 components 選項中定義你想要使用的組件:

new Vue({
el: '#app',
components: {
'component-a': ComponentA,
'component-b': ComponentB
}
})

對於 components 對象中的每一個屬性來講,其屬性名就是自定義元素的名字,其屬性值就是這個組件的選項對象。

注意局部註冊的組件在其子組件中不可用。例如,若是你但願 ComponentA 在 ComponentB 中可用,則你須要這樣寫:

var ComponentA = { /* ... */ }

var ComponentB = {
components: {
'component-a': ComponentA
},
// ...
}

或者若是你經過 Babel 和 webpack 使用 ES2015 模塊,那麼代碼看起來更像:

import ComponentA from './ComponentA.vue'

export default {
components: {
ComponentA
},
// ...
}

注意在 ES2015+ 中,在對象中放一個相似 ComponentA 的變量名實際上是 ComponentA: ComponentA 的縮寫,即這個變量名同時是:

  • 用在模板中的自定義元素的名稱
  • 包含了這個組件選項的變量名

模塊系統

若是你沒有經過 import/require 使用一個模塊系統,也許能夠暫且跳過這個章節。若是你使用了,那麼咱們會爲你提供一些特殊的使用說明和注意事項。

在模塊系統中局部註冊

若是你還在閱讀,說明你使用了諸如 Babel 和 webpack 的模塊系統。在這些狀況下,咱們推薦建立一個 components 目錄,並將每一個組件放置在其各自的文件中。

而後你須要在局部註冊以前導入每一個你想使用的組件。例如,在一個假設的 ComponentB.js 或 ComponentB.vue 文件中:

import ComponentA from './ComponentA'
import ComponentC from './ComponentC'

export default {
components: {
ComponentA,
ComponentC
},
// ...
}

如今 ComponentA 和 ComponentC 均可以在 ComponentB 的模板中使用了。

基礎組件的自動化全局註冊

可能你的許多組件只是包裹了一個輸入框或按鈕之類的元素,是相對通用的。咱們有時候會把它們稱爲基礎組件,它們會在各個組件中被頻繁的用到。

因此會致使不少組件裏都會有一個包含基礎組件的長列表:

import BaseButton from './BaseButton.vue'
import BaseIcon from './BaseIcon.vue'
import BaseInput from './BaseInput.vue'

export default {
components: {
BaseButton,
BaseIcon,
BaseInput
}
}

而只是用於模板中的一小部分:

<BaseInput
v-model="searchText"
@keydown.enter="search"
/>
<BaseButton @click="search">
<BaseIcon name="search"/>
</BaseButton>

幸虧若是你使用了 webpack (或在內部使用了 webpack 的 Vue CLI 3+),那麼就可使用 require.context 只全局註冊這些很是通用的基礎組件。這裏有一份可讓你在應用入口文件 (好比 src/main.js) 中全局導入基礎組件的示例代碼:

import Vue from 'vue'
import upperFirst from 'lodash/upperFirst'
import camelCase from 'lodash/camelCase'

const requireComponent = require.context(
// 其組件目錄的相對路徑
'./components',
// 是否查詢其子目錄
false,
// 匹配基礎組件文件名的正則表達式
/Base[A-Z]\w+\.(vue|js)$/
)

requireComponent.keys().forEach(fileName => {
// 獲取組件配置
const componentConfig = requireComponent(fileName)

// 獲取組件的 PascalCase 命名
const componentName = upperFirst(
camelCase(
// 剝去文件名開頭的 `./` 和結尾的擴展名
fileName.replace(/^\.\/(.*)\.\w+$/, '$1')
)
)

// 全局註冊組件
Vue.component(
componentName,
// 若是這個組件選項是經過 `export default` 導出的,
// 那麼就會優先使用 `.default`,
// 不然回退到使用模塊的根。
componentConfig.default || componentConfig
)
})

記住全局註冊的行爲必須在根 Vue 實例 (經過 new Vue) 建立以前發生。這裏有一個真實項目情景下的示例。

 

Prop

該頁面假設你已經閱讀過了組件基礎。若是你還對組件不太瞭解,推薦你先閱讀它。

Prop 的大小寫 (camelCase vs kebab-case)

HTML 中的特性名是大小寫不敏感的,因此瀏覽器會把全部大寫字符解釋爲小寫字符。這意味着當你使用 DOM 中的模板時,camelCase (駝峯命名法) 的 prop 名須要使用其等價的 kebab-case (短橫線分隔命名) 命名:

Vue.component('blog-post', {
// 在 JavaScript 中是 camelCase 的
props: ['postTitle'],
template: '<h3>{{ postTitle }}</h3>'
})
<!-- 在 HTML 中是 kebab-case 的 -->
<blog-post post-title="hello!"></blog-post>

重申一次,若是你使用字符串模板,那麼這個限制就不存在了。

Prop 類型

到這裏,咱們只看到了以字符串數組形式列出的 prop:

props: ['title', 'likes', 'isPublished', 'commentIds', 'author']

可是,一般你但願每一個 prop 都有指定的值類型。這時,你能夠以對象形式列出 prop,這些屬性的名稱和值分別是 prop 各自的名稱和類型:

props: {
title: String,
likes: Number,
isPublished: Boolean,
commentIds: Array,
author: Object
}

這不只爲你的組件提供了文檔,還會在它們遇到錯誤的類型時從瀏覽器的 JavaScript 控制檯提示用戶。你會在這個頁面接下來的部分看到類型檢查和其它 prop 驗證

傳遞靜態或動態 Prop

像這樣,你已經知道了能夠像這樣給 prop 傳入一個靜態的值:

<blog-post title="My journey with Vue"></blog-post>

你也知道 prop 能夠經過 v-bind 動態賦值,例如:

<!-- 動態賦予一個變量的值 -->
<blog-post v-bind:title="post.title"></blog-post>

<!-- 動態賦予一個複雜表達式的值 -->
<blog-post v-bind:title="post.title + ' by ' + post.author.name"></blog-post>

在上述兩個示例中,咱們傳入的值都是字符串類型的,但實際上任何類型的值均可以傳給一個 prop。

傳入一個數字

<!-- 即使 `42` 是靜態的,咱們仍然須要 `v-bind` 來告訴 Vue -->
<!-- 這是一個 JavaScript 表達式而不是一個字符串。-->
<blog-post v-bind:likes="42"></blog-post>

<!-- 用一個變量進行動態賦值。-->
<blog-post v-bind:likes="post.likes"></blog-post>

傳入一個布爾值

<!-- 包含該 prop 沒有值的狀況在內,都意味着 `true`。-->
<blog-post is-published></blog-post>

<!-- 即使 `false` 是靜態的,咱們仍然須要 `v-bind` 來告訴 Vue -->
<!-- 這是一個 JavaScript 表達式而不是一個字符串。-->
<blog-post v-bind:is-published="false"></blog-post>

<!-- 用一個變量進行動態賦值。-->
<blog-post v-bind:is-published="post.isPublished"></blog-post>

傳入一個數組

<!-- 即使數組是靜態的,咱們仍然須要 `v-bind` 來告訴 Vue -->
<!-- 這是一個 JavaScript 表達式而不是一個字符串。-->
<blog-post v-bind:comment-ids="[234, 266, 273]"></blog-post>

<!-- 用一個變量進行動態賦值。-->
<blog-post v-bind:comment-ids="post.commentIds"></blog-post>

傳入一個對象

<!-- 即使對象是靜態的,咱們仍然須要 `v-bind` 來告訴 Vue -->
<!-- 這是一個 JavaScript 表達式而不是一個字符串。-->
<blog-post v-bind:author="{ name: 'Veronica', company: 'Veridian Dynamics' }"></blog-post>

<!-- 用一個變量進行動態賦值。-->
<blog-post v-bind:author="post.author"></blog-post>

傳入一個對象的全部屬性

若是你想要將一個對象的全部屬性都做爲 prop 傳入,你可使用不帶參數的 v-bind(取代 v-bind:prop-name)。例如,對於一個給定的對象 post

post: {
id: 1,
title: 'My Journey with Vue'
}

下面的模板:

<blog-post v-bind="post"></blog-post>

等價於:

<blog-post
v-bind:id="post.id"
v-bind:title="post.title"
></blog-post>

單向數據流

全部的 prop 都使得其父子 prop 之間造成了一個單向下行綁定:父級 prop 的更新會向下流動到子組件中,可是反過來則不行。這樣會防止從子組件意外改變父級組件的狀態,從而致使你的應用的數據流向難以理解。

額外的,每次父級組件發生更新時,子組件中全部的 prop 都將會刷新爲最新的值。這意味着你不該該在一個子組件內部改變 prop。若是你這樣作了,Vue 會在瀏覽器的控制檯中發出警告。

這裏有兩種常見的試圖改變一個 prop 的情形:

  1. 這個 prop 用來傳遞一個初始值;這個子組件接下來但願將其做爲一個本地的 prop 數據來使用。在這種狀況下,最好定義一個本地的 data 屬性並將這個 prop 用做其初始值:

    props: ['initialCounter'],
    data: function () {
    return {
    counter: this.initialCounter
    }
    }
  2. 這個 prop 以一種原始的值傳入且須要進行轉換。在這種狀況下,最好使用這個 prop 的值來定義一個計算屬性:

    props: ['size'],
    computed: {
    normalizedSize: function () {
    return this.size.trim().toLowerCase()
    }
    }

注意在 JavaScript 中對象和數組是經過引用傳入的,因此對於一個數組或對象類型的 prop 來講,在子組件中改變這個對象或數組自己將會影響到父組件的狀態。

Prop 驗證

咱們能夠爲組件的 prop 指定驗證要求,例如你知道的這些類型。若是有一個需求沒有被知足,則 Vue 會在瀏覽器控制檯中警告你。這在開發一個會被別人用到的組件時尤爲有幫助。

爲了定製 prop 的驗證方式,你能夠爲 props 中的值提供一個帶有驗證需求的對象,而不是一個字符串數組。例如:

Vue.component('my-component', {
props: {
// 基礎的類型檢查 (`null` 匹配任何類型)
propA: Number,
// 多個可能的類型
propB: [String, Number],
// 必填的字符串
propC: {
type: String,
required: true
},
// 帶有默認值的數字
propD: {
type: Number,
default: 100
},
// 帶有默認值的對象
propE: {
type: Object,
// 對象或數組默認值必須從一個工廠函數獲取
default: function () {
return { message: 'hello' }
}
},
// 自定義驗證函數
propF: {
validator: function (value) {
// 這個值必須匹配下列字符串中的一個
return ['success', 'warning', 'danger'].indexOf(value) !== -1
}
}
}
})

當 prop 驗證失敗的時候,(開發環境構建版本的) Vue 將會產生一個控制檯的警告。

注意那些 prop 會在一個組件實例建立以前進行驗證,因此實例的屬性 (如 datacomputed 等) 在 default 或 validator 函數中是不可用的。

類型檢查

type 能夠是下列原生構造函數中的一個:

  • String
  • Number
  • Boolean
  • Array
  • Object
  • Date
  • Function
  • Symbol

額外的,type 還能夠是一個自定義的構造函數,而且經過 instanceof 來進行檢查確認。例如,給定下列現成的構造函數:

function Person (firstName, lastName) {
this.firstName = firstName
this.lastName = lastName
}

你可使用:

Vue.component('blog-post', {
props: {
author: Person
}
})

來驗證 author prop 的值是不是經過 new Person 建立的。

非 Prop 的特性

一個非 prop 特性是指傳向一個組件,可是該組件並無相應 prop 定義的特性。

由於顯式定義的 prop 適用於向一個子組件傳入信息,然而組件庫的做者並不總能預見組件會被用於怎樣的場景。這也是爲何組件能夠接受任意的特性,而這些特性會被添加到這個組件的根元素上。

例如,想象一下你經過一個 Bootstrap 插件使用了一個第三方的 <bootstrap-date-input> 組件,這個插件須要在其 <input> 上用到一個 data-date-picker 特性。咱們能夠將這個特性添加到你的組件實例上:

<bootstrap-date-input data-date-picker="activated"></bootstrap-date-input>

而後這個 data-date-picker="activated" 特性就會自動添加到 <bootstrap-date-input> 的根元素上。

替換/合併已有的特性

想象一下 <bootstrap-date-input> 的模板是這樣的:

<input type="date" class="form-control">

爲了給咱們的日期選擇器插件定製一個主題,咱們可能須要像這樣添加一個特別的類名:

<bootstrap-date-input
data-date-picker="activated"
class="date-picker-theme-dark"
></bootstrap-date-input>

在這種狀況下,咱們定義了兩個不一樣的 class 的值:

  • form-control,這是在組件的模板內設置好的
  • date-picker-theme-dark,這是從組件的父級傳入的

對於絕大多數特性來講,從外部提供給組件的值會替換掉組件內部設置好的值。因此若是傳入 type="text" 就會替換掉 type="date" 並把它破壞!慶幸的是,class和 style 特性會稍微智能一些,即兩邊的值會被合併起來,從而獲得最終的值:form-control date-picker-theme-dark

禁用特性繼承

若是你不但願組件的根元素繼承特性,你能夠在組件的選項中設置 inheritAttrs: false。例如:

Vue.component('my-component', {
inheritAttrs: false,
// ...
})

這尤爲適合配合實例的 $attrs 屬性使用,該屬性包含了傳遞給一個組件的特性名和特性值,例如:

{
class: 'username-input',
placeholder: 'Enter your username'
}

有了 inheritAttrs: false 和 $attrs,你就能夠手動決定這些特性會被賦予哪一個元素。在撰寫基礎組件的時候是常會用到的:

Vue.component('base-input', {
inheritAttrs: false,
props: ['label', 'value'],
template: `
<label>
{{ label }}
<input
v-bind="$attrs"
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
>
</label>
`
})

這個模式容許你在使用基礎組件的時候更像是使用原始的 HTML 元素,而不會擔憂哪一個元素是真正的根元素:

<base-input
v-model="username"
class="username-input"
placeholder="Enter your username"
></base-input>
相關文章
相關標籤/搜索