學習筆記: 組件詳解
Vue組件須要註冊後纔可使用。註冊有全局註冊和局部註冊兩種方式。html
全局註冊git
Vue.component('my-component', {});
要在父實例中使用這個組件,必需要在實例建立前註冊,以後就能夠用<my-component></my-component>
的形式來使用組件。github
Vue.component('my-component', { template: `<div>這是一個組件</div>` });
template
的DOM結構必須被一個元素包含,缺乏<div></div>
會沒法渲染並報錯。設計模式
在Vue實例中,使用components
選項能夠局部註冊組件,註冊後的組件只在該實例做用域下有效。數組
組件中也可使用components
選項來註冊組件,使組件能夠嵌套。瀏覽器
var Child = { template: `<div>局部註冊組件的內容</div>` }; new Vue({ el: '#app', components: { 'my-component': Child }, });
Vue組件的模板在某些狀況下會受到HTML的限制,好比<table>
內規定只容許是<tr>
、<td>
、<th>
等這些表格元素,因此在<table>
內直接使用組件時無效的。這種狀況下,可使用特殊的is
屬性來掛載組件。緩存
<div id="app"> <table> <tbody is="my-component"></tbody> </table> </div> Vue.component('my-component', { template: `<div>這裏是組件內容</div>` });
常見的限制元素還有<ul>
、<ol>
、<select>
。服務器
除了template
選項外,組件中還能夠像Vue實例那樣使用其餘的選項,好比data
、computed
、methods
等。app
可是在使用data
時,data
必須是函數,而後將數據return
出去。frontend
JavaScript對象是引用關係,若是return
的對象引用了外部的一個對象,那這個對象就是共享的,任何一方修改都會同步。
props
傳遞數據組件不只要把模板的內容進行復用,更重要的是組件間進行通訊。
一般父組件的模板中包含子組件,父組件要正向地向子組件傳遞數據或參數,子組件接收後根據參數的不一樣渲染不一樣的內容或者執行操做。這個正向傳遞數據的過程經過props
來實現。
在組件中,使用選項props
聲明須要從父級接收的數據,props
的值能夠是兩種,一種是字符串數組,一種是對象。
<my-component message="來自父組件的數據"></my-component> props: ['message'], template: `<div>{{message}}</div>`,
props
中聲明的數據與組件data
函數中return
的數據主要區別就是props
的數據來自父級,而data
中的是組件本身的數據,做用域是組件自己,這兩種數據均可以在模板template
及計算屬性computed
和方法methods
中使用。
因爲HTML特性不區分大小寫,當使用DOM模板時,駝峯命名的props
名稱要轉爲短橫線分割命名。
<my-component warning-text="提示信息"></my-component>
有時候,傳遞的數據並非直接寫死,而是來自父級的動態數據,這時可使用指令v-bind
動態綁定props
的值,當父組件的數據變化時,也會傳遞子組件。
<div id="app"> <input type="text" v-model="parentMessage"> <my-component :message="parentMessage"></my-component> </div> props: ['message'], template: `<div>{{message}}</div>`, data: { parentMessage: '' }
這裏用v-model
綁定了父級的數據parentMessage
,當經過輸入框任意輸入時,子組件接收到的props["message"]
也會實時響應,並更新組件模板。
業務中會常常遇到兩種須要改變prop
的狀況,一種是父組件傳遞初始值進來,子組件將它做爲初始值保存起來,在本身的做用域下能夠隨意使用和修改。這種狀況能夠在組件data
內再聲明一個數據,引用父組件的prop
。
<div id="app"> <my-component :init-count="1"></my-component> </div> Vue.component('my-component', { props: ['initCount'], template: `<div>{{count}}</div>`, data() { return { count:this.initCount } } });
組件中聲明瞭數據count
,它在組件初始化時會獲取來自父組件的initCount
,以後就與之無關了,只用維護count
,這樣就能夠避免直接操做initCount
。
另外一種狀況就是prop
做爲須要被轉變的原始值傳入,這種狀況用計算屬性就能夠。
<div id="app"> <my-component :width="100"></my-component> </div> Vue.component('my-component', { props: ['width'], template: `<div :style="style">組件內容</div>`, computed: { style: function () { return { width: this.width + 'px' } } } });
由於用CSS傳遞寬度要帶單位(px),數值計算通常不帶單位,因此統一在組件內使用計算屬性。
在JavaScript中對象和數組時引用類型,指向同一個內存空間,因此
props
是對象和數組時,在子組件內改變是會影響父組件。
當prop
須要驗證時,須要對象寫法。
通常當組件須要提供給別人使用時,推薦都進行數據驗證。好比某個數據必須是數字類型,若是傳入字符串,就會在控制檯彈出警告。
<p data-height="565" data-theme-id="0" data-slug-hash="WywyjV" data-default-tab="js" data-user="whjin" data-embed-version="2" data-pen-title="prop" class="codepen">See the Pen prop by whjin (@whjin) on CodePen.</p>
<script async src="https://static.codepen.io/ass...;></script>
驗證的type
類型能夠是:
String
Number
Boolean
Object
Array
Function
type
也能夠是一個自定義構造器,使用instanceof
檢測。
組件關係可分爲父組件通訊、兄弟組件通訊、跨級組件通訊。
當子組件須要向父組件傳遞數據時,就要用到自定義事件。
v-on
除了監聽DOM事件外,還能夠用於組件之間的自定義事件。
JavaScript的設計模式——觀察者模式方法:
dispatchEvent
addEventListener
Vue組件的子組件用$emit()
來觸發事件,父組件用$on()
來監聽子組件的事件。
父組件也能夠直接在子組件的自定義標籤上使用v-on
來監聽子組件觸發的自定義事件。
<p data-height="365" data-theme-id="0" data-slug-hash="ZRWjKv" data-default-tab="js,result" data-user="whjin" data-embed-version="2" data-pen-title="自定義事件" class="codepen">See the Pen 自定義事件 by whjin (@whjin) on CodePen.</p>
<script async src="https://static.codepen.io/ass...;></script>
在改變組件的data "counter"
後,經過$emit()
再把它傳遞給父組件,父組件用@increase
和@reduce
。$emit()
方法的第一個參數是自定義事件的名稱。
除了用v-on
在組件上監聽自定義事件外,也能夠監聽DOM事件,這時能夠用.native
修飾符表示監聽時一個原生事件,監聽的是該組件的根元素。
<my-component @click:native="handleClick"></my-component>
v-model
Vue能夠在自定義組件上使用v-model
指令。
<my-component v-model="total"></my-component>
組件$emit()
的事件名時特殊的input
,在使用組件的父級,並無在<my-component>
上使用@input="handler"
,而是直接用了v-model
綁定的一個數據total
。
<my-component @input="handleGetTotal"></my-component>
v-model
還能夠用來建立自定義的表單輸入組件,進行數據雙向綁定。
<p data-height="365" data-theme-id="0" data-slug-hash="zaqJBQ" data-default-tab="html,result" data-user="whjin" data-embed-version="2" data-pen-title="v-model雙向綁定" class="codepen">See the Pen v-model雙向綁定 by whjin (@whjin) on CodePen.</p>
<script async src="https://static.codepen.io/ass...;></script>
實現這樣一個具備雙向綁定的v-model
組件要知足下面兩個要求:
value
屬性value
時觸發input
事件在實際業務中,除了父子組件通訊外,還有不少非父子組件通訊的場景,非父子組件通常有兩種,兄弟組件和跨多級組件。
在Vue 1.x版本中,除了$emit()
方法外,還提供了¥dispatch()
和$broadcast()
。
$dispatch()
用於向上級派發事件,只要是它的父級(一級或多級以上),均可以在Vue實例的events
選項內接收。
此實例只在Vue 1.x版本中有效:
<p data-height="365" data-theme-id="0" data-slug-hash="pKyOOY" data-default-tab="html,result" data-user="whjin" data-embed-version="2" data-pen-title="dispatch派發事件" class="codepen">See the Pen dispatch派發事件 by whjin (@whjin) on CodePen.</p>
<script async src="https://static.codepen.io/ass...;></script>
$broadcast()
是由上級向下級廣播事件,用法徹底一致,方向相反。
這兩種方法一旦發出事件後,任何組件均可以接收到,就近原則,並且會在第一次接收到後中止冒泡,除非返回true
。
這些方法在Vue 2.x版本中已廢棄。
在Vue 2.x中,推薦任何一個空的Vue實例做爲中央事件總線(bus
),也就是一箇中介。
<p data-height="365" data-theme-id="0" data-slug-hash="dKMgvJ" data-default-tab="html,result" data-user="whjin" data-embed-version="2" data-pen-title="Vue-bus事件總線" class="codepen">See the Pen Vue-bus事件總線 by whjin (@whjin) on CodePen.</p>
<script async src="https://static.codepen.io/ass...;></script>
首先建立了一個名爲bus
的空的Vue實例;而後全局定義了組件component-a
;最後建立了Vue實例app
。
在app
初始化時,也就是在生命週期mounted
鉤子函數裏監聽了來自bus
的事件on-message
,而在組件component-a
中,點擊按鈕後會經過bus
把事件on-message
發出去。此時app
就會接收到來自bus
的事件,進而在回調裏完成本身的業務邏輯。
這種方法巧妙而輕量地實現了任何組件間的通訊,包括父子、兄弟、跨級。
若是深刻使用,能夠擴展bus
實例,給它添加data
、methods
、computed
等選項,這些都是能夠公用的。
在業務中,尤爲是協同開發時很是有用,由於常常須要共享一些通用的信息,好比用戶登陸的暱稱、性別、郵箱等,還有用戶的受權token
等。
只需在初始化時讓bus
獲取一次,任什麼時候間、任何組件就能夠從中直接使用,在單頁面富應用(SPA)中會很實用。
除了中央事件總線bus
外,還有兩種方法能夠實現組件間通訊:父鏈和子組件索引。
在子組件中,使用this.$parent
能夠直接訪問該組件的父實例或組件,父組件也能夠經過this.$children
訪問它全部的子組件,並且能夠遞歸向上或向下無限訪問,直到根實例或最內層的組件。
<div id="app"> <p>{{message}}</p> <component-a></component-a> </div> Vue.component('component-a', { template: `<button @click="handleEvent">經過父鏈直接修改數據</button>`, methods: { handleEvent: function () { this.$parent.message = '來自組件component-a的內容' } } }); var app = new Vue({ el: '#app', data: { message: '' } });
儘管Vue容許這樣操做,但在業務中,子組件應該儘量地避免依賴父組件的數據,更不該該去主動修改它的數據,由於這樣使得父子組件緊耦合,只看父組件,很難理解父組件的狀態,由於它可能被任意組件修改,理想狀態下,只有組件本身能修改它的狀態。
父子組件最好仍是經過props
和$emit()
來通訊。
當子組件較多時,經過this.$children
來遍歷出須要的一個組件實例是比較困難的,尤爲是組件動態渲染時,它們的序列是不固定的。
Vue提供了子組件索引的方法,用特殊的屬性ref
來爲子組件指定一個索引名稱。
<p data-height="365" data-theme-id="0" data-slug-hash="dKMLXY" data-default-tab="html,result" data-user="whjin" data-embed-version="2" data-pen-title="Vue-$refs" class="codepen">See the Pen <a href="https://codepen.io/whjin/pen/dKMLXY/">Vue-$refs</a> by whjin (@whjin) on CodePen.</p>
<script async src="https://static.codepen.io/ass...;></script>
在父組件模板中,子組件標籤上使用ref
指定一個名稱,並在父組件內經過this.$refs
來訪問指定名稱的子組件。
$refs
只在組件渲染完成後才填充,而且它是非響應式的。它僅僅做爲一個直接訪問子組件的應急方案,應當避免在模板或計算屬性中使用$refs
。
Vue 2.x將v-el
和v-ref
合併成ref
,Vue會自動去判斷是普通標籤仍是組件。
slot
分發內容當須要讓組件組合使用,混合父組件的內容與子組件的模板時,就會用到slot
,這個過程叫作內容分發。
<app>
組件不知道它的掛載點會有什麼內容。掛載點的內容是由<app>
的父組件決定的。<app>
組件極可能有它本身的模板。props
傳遞數據、events
觸發事件和slot
內容分發就構成了Vue組件的3個API來源,再複雜的組件也是由這3部分構成。
父組件中的模板:
<child-component> {{message}} </child-component>
這裏的message
就是一個slot
,可是它綁定的是父組件的數據,而不是組件<child-component>
的數據。
父組件模板的內容是在父組件做用域內編譯,子組件模板的內容是在子組件做用域內編譯。
<div id="app"> <child-component v-modle="showChild"></child-component> </div> Vue.component('child-component', { template: `<div>子組件1</div>`, }); var app = new Vue({ el: '#app', data: { showChild: true } });
這裏的狀態showChild
綁定的是父組件的數據。
在子組件上綁定數據:
<div id="app"> <child-component></child-component> </div> Vue.component('child-component', { template: `<div v-model="showChild">子組件</div>`, data() { return { showChild: true } } }); var app = new Vue({ el: '#app', });
所以,slot
分發的內容,做用域是在父組件上。
slot
在子組件內使用特殊的<slot>
元素就能夠爲這個組件開啓一個slot
(插槽),在父組件模板裏,插入在子組件標籤內的全部內容將替代子組件的<slot>
標籤及它的內容。
<div id="app"> <child-component> <p>分發的內容</p> <p>更多分發的內容</p> </child-component> </div> Vue.component('child-component', { template: ` <div> <slot> <p>若是沒有父組件插入內容,我將做爲默認出現。</p> </slot> </div> `, }); var app = new Vue({ el: '#app', });
子組件child-component
的模板內定義了一個<slot>
元素,而且用一個<p>
做爲默認的內容,在父組件沒有使用slot
時,會渲染這段默認的文本;若是寫入了slot
,就會替換整個<slot>
。
子組件
<slot>
內的備用內容,它的做用域是子組件自己。
Slot
給<slot>
元素指定一個name
後能夠分發多個內容,具名slot
能夠與單個slot
共存。
<p data-height="265" data-theme-id="0" data-slug-hash="RJRVQJ" data-default-tab="html,result" data-user="whjin" data-embed-version="2" data-pen-title="Vue-slot" class="codepen">See the Pen Vue-slot by whjin (@whjin) on CodePen.</p>
<script async src="https://static.codepen.io/ass...;></script>
子組件內聲明瞭3個<slot>
元素,其中在<div class="main">
內的<slot>
沒有使用name
特性,它將做爲默認slot
出現,父組件沒有使用slot
特性的元素與內容都將出如今這裏。
若是沒有指定默認的匿名slot
,父組件內多餘的內容都將被拋棄。
在組合使用組件時,內容分發API相當重要。
做用域插槽是一種特殊的slot
,使用一個能夠複用的模板替換已渲染元素。
<div id="app"> <child-component> <template scope="props"> <p>來自父組件的內容</p> <p>{{props.msg}}</p> </template> </child-component> </div> Vue.component('child-component', { template: ` <div class="container"> <slot msg="來自子組件的內容"></slot> </div> `, }); var app = new Vue({ el: '#app', });
子組件的模板,在<slot>
元素上有一個相似props
傳遞數據給組件的寫法msg="xxx"
,將數據傳遞到插槽。
父組件中使用了<template>
元素,並且擁有一個scope="props"
的特性,這裏的props
是一個臨時變量。
template
內能夠經過臨時變量props
訪問來自子組件插槽的數據msg
。
做用域插槽更具表明性的用例是列表組件,容許組件自定義應該如何渲染列表每一項。
<div id="app"> <my-list :book="books"> <!--做用域插槽也能夠是具名的Slot--> <template slot="book" scope="props"> <li>{{props.bookName}}</li> </template> </my-list> </div> Vue.component('my-list', { props: { books: { type: Array, default: function () { return []; } } }, template: ` <ul> <slot name="book" v-for="book in books" :book-name="book.name"></slot> </ul> `, });
子組件my-list
接收一個來自父級的prop
數組books
,而且將它在name
爲book
的slot
上使用v-for
指令循環,同時暴露一個變量bookName
。
做用域插槽的使用場景是既能夠複用子組件的slot
,又可使slot
內容不一致。
slot
Vue 2.x提供了用來訪問被slot
分發的內容的方法$slots
。
<p data-height="365" data-theme-id="0" data-slug-hash="vrKZew" data-default-tab="html,result" data-user="whjin" data-embed-version="2" data-pen-title="Vue-$slots" class="codepen">See the Pen <a href="https://codepen.io/whjin/pen/vrKZew/">Vue-$slots</a> by whjin (@whjin) on CodePen.</p>
<script async src="https://static.codepen.io/ass...;></script>
經過$slots
能夠訪問某個具名slot
,this.$slots.default
包括了全部沒有被包含在具名slot
中的節點。
給組件設置name
選項,組件在它的模板內能夠遞歸地調用本身。
<div id="app"> <child-component :count="1"></child-component> </div> Vue.component('child-component', { name: 'child-component', props: { count: { type: Number, default: 1 } }, template: ` <div class="child"> <child-component :count="count+1" v-if="count<3"></child-component> </div> `, });
組件遞歸使用能夠用來開發一些具備未知層級關機的獨立組件,好比級聯選擇器和樹形控件等。
組件的模板通常都是在template
選項內定義的,Vue提供了一個內聯模板的功能,在使用組件時,給組件標籤使用inline-template
特性,組件就會把它的內容當作模板,而不是把它當內容分發,這讓模板更靈活。
<p data-height="265" data-theme-id="0" data-slug-hash="OEXjLb" data-default-tab="html,result" data-user="whjin" data-embed-version="2" data-pen-title="Vue-inline-template" class="codepen">See the Pen Vue-inline-template by whjin (@whjin) on CodePen.</p>
<script async src="https://static.codepen.io/ass...;></script>
在父組件中聲明的數據message
和子組件中聲明的數據msg
,兩個均可以渲染(若是同名,優先使用子組件的數據)。這是內聯模板的缺點,就是做用域比較難理解,若是不是很是特殊的場景,建議不要輕易使用內聯模板。
Vue.js提供了一個特殊的元素<component>
用來動態地掛載不一樣的組件,使用is
特性來選擇要掛載的組件。
<p data-height="365" data-theme-id="0" data-slug-hash="zaBdyY" data-default-tab="html,result" data-user="whjin" data-embed-version="2" data-pen-title="Vue-component" class="codepen">See the Pen Vue-component by whjin (@whjin) on CodePen.</p>
<script async src="https://static.codepen.io/ass...;></script>
能夠直接綁定在組件對象上:
<div id="app"> <component :is="currentView"></component> </div> var Home = { template: `<p>Welcome home!</p>` }; var app = new Vue({ el: '#app', data: { currentView: Home } });
Vue.js容許將組件定義爲一個工廠函數,動態地解析組件。
Vue.js只在組件須要渲染時觸發工廠函數,而且把結果緩存起來,用於後面的再次渲染。
<div id="app"> <child-component></child-component> </div> Vue.component('child-component', function (resolve, reject) { window.setTimeout(function () { resolve({ template: `<div>我是異步渲染的!</div>` }) }, 1000) }); var app = new Vue({ el: '#app', });
工廠函數接收一個resolve
回調,在收到從服務器下載的組件定義時調用。也能夠調用reject(reason)
指示加載失敗。
$nextTick
異步更新隊列
Vue在觀察到數據變化時並非直接更新DOM,而是開啓一個隊列,並緩衝在同一個事件循環中發生的全部數據變化。在緩衝時會去除重複數據,從而避免沒必要要的計算和DOM操做。而後,在一下個事件循環tick
中,Vue刷新隊列並執行實際(已去重的)工做。
Vue會根據當前瀏覽器環境優先使用原生的Promise.then
和MutationObserver
,若是都不支持,就會採用setTimeout
代替。
$nextTick
就是用來肯定何時DOM更新已經完成。
<p data-height="365" data-theme-id="0" data-slug-hash="RJRjgm" data-default-tab="html,result" data-user="whjin" data-embed-version="2" data-pen-title="Vue-$nextTick" class="codepen">See the Pen <a href="https://codepen.io/whjin/pen/RJRjgm/">Vue-$nextTick</a> by whjin (@whjin) on CodePen.</p>
<script async src="https://static.codepen.io/ass...;></script>
X-Templates
Vue提供了另外一種定義模板的方式,在<script>
標籤中使用text/x-template
類型,而且指定一個id
,將這個id
賦給template
。
<div id="app"> <my-component></my-component> <script type="text/x-template" id="my-component"> <div>這是組件的內容</div> </script> </div> Vue.component('my-component', { template: `#my-component`, }); var app = new Vue({ el: '#app', });
在一些很是特殊的狀況下,須要動態地建立Vue實例,Vue提供了Vue.extend
和$mount
兩個方法來手動掛載一個實例。
Vue.extend
是基礎Vue構造器,建立一個「子類」,參數是一個包含組件選項的對象。
若是Vue實例在實例化時沒有收到el
選項,它就處於「未掛載」狀態,沒有關聯的DOM元素。可使用$mount
手動地掛載一個未掛載的實例。這個方法返回實例自身,於是能夠鏈式調用其餘實例方法。
<p data-height="265" data-theme-id="0" data-slug-hash="BVzmbL" data-default-tab="html,result" data-user="whjin" data-embed-version="2" data-pen-title="Vue-$mount" class="codepen">See the Pen <a href="https://codepen.io/whjin/pen/BVzmbL/">Vue-$mount</a> by whjin (@whjin) on CodePen.</p>
<script async src="https://static.codepen.io/ass...;></script>
除了以上寫法外,還有兩種寫法:
new MyComponent().$mount("#app"); new MyComponent({ el: '#app' })
手動掛載實例(組件)是一種比較極端的高級用法,在業務中幾乎用不到,只在開發一些複雜的獨立組件時可能會使用。