一個再複雜的組件,都是由三部分組成的:prop、event、slot,它們構成了 Vue.js 組件的 API。若是你開發的是一個通用組件,那必定要事先設計好這三部分,由於組件一旦發佈,後面再修改 API 就很困難了,使用者都是但願不斷新增功能,修復 bug,而不是常常變動接口。若是你閱讀別人寫的組件,也能夠從這三個部分展開,它們能夠幫助你快速瞭解一個組件的全部功能。html
prop
定義了這個組件有哪些可配置的屬性,組件的核心功能也都是它來肯定的。寫通用組件時,props 最好用對象的寫法,這樣能夠針對每一個屬性設置類型、默認值或自定義校驗屬性的值,這點在組件開發中很重要,然而不少人卻忽視,直接使用 props 的數組用法,這樣的組件每每是不嚴謹的。好比咱們封裝一個按鈕組件 <i-button>
:vue
1 <template> 2 <button :class="'i-button-size' + size" :disabled="disabled"></button> 3 </template> 4 <script> 5 // 判斷參數是不是其中之一 6 function oneOf (value, validList) { 7 for (let i = 0; i < validList.length; i++) { 8 if (value === validList[i]) { 9 return true; 10 } 11 } 12 return false; 13 } 14 15 export default { 16 props: { 17 size: { 18 validator (value) { 19 return oneOf(value, ['small', 'large', 'default']); 20 }, 21 default: 'default' 22 }, 23 disabled: { 24 type: Boolean, 25 default: false 26 } 27 } 28 } 29 </script>
使用組件:數組
<i-button size="large"></i-button> <i-button disabled></i-button>
組件中定義了兩個屬性:尺寸 size 和 是否禁用 disabled。其中 size 使用 validator
進行了值的自定義驗證,也就是說,從父級傳入的 size,它的值必須是指定的 small、large、default 中的一個,默認值是 default,若是傳入這三個之外的值,都會拋出一條警告。工具
要注意的是,組件裏定義的 props,都是單向數據流,也就是隻能經過父級修改,組件本身不能修改 props 的值,只能修改定義在 data 裏的數據,非要修改,也是經過後面介紹的自定義事件通知父級,由父級來修改。this
在使用組件時,也能夠傳入一些標準的 html 特性,好比 id、class:spa
<i-button id="btn1" class="btn-submit"></i-button>
這樣的 html 特性,在組件內的 <button>
元素上會繼承,並不須要在 props 裏再定義一遍。這個特性是默認支持的,若是不指望開啓,在組件選項裏配置 inheritAttrs: false
就能夠禁用了。插件
若是要給上面的按鈕組件 <i-button>
添加一些文字內容,就要用到組件的第二個 API:插槽 slot,它能夠分發組件的內容,好比在上面的按鈕組件中定義一個插槽:設計
<template> <button :class="'i-button-size' + size" :disabled="disabled"> <slot></slot> </button> </template>
這裏的 <slot>
節點就是指定的一個插槽的位置,這樣在組件內部就能夠擴展內容了:code
<i-button>按鈕 1</i-button> <i-button> <strong>按鈕 2</strong> </i-button>
當須要多個插槽時,會用到具名 slot,好比上面的組件咱們再增長一個 slot,用於設置另外一個圖標組件:component
<template> <button :class="'i-button-size' + size" :disabled="disabled"> <slot name="icon"></slot> <slot></slot> </button> </template>
<i-button>
<i-icon slot="icon" type="checkmark"></i-icon>
按鈕 1
</i-button>
這樣,父級內定義的內容,就會出如今組件對應的 slot 裏,沒有寫名字的,就是默認的 slot。
在組件的 <slot>
裏也能夠寫一些默認的內容,這樣在父級沒有寫任何 slot 時,它們就會出現,好比:
<slot>提交</slot>
如今咱們給組件 <i-button>
加一個點擊事件,目前有兩種寫法,咱們先看自定義事件 event(部分代碼省略):
<template> <button @click="handleClick"> <slot></slot> </button> </template> <script> export default { methods: { handleClick (event) { this.$emit('on-click', event); } } } </script>
經過 $emit
,就能夠觸發自定義的事件 on-click
,在父級經過 @on-click
來監聽:
<i-button @on-click="handleClick"></i-button>
上面的 click 事件,是在組件內部的 <button>
元素上聲明的,這裏還有另外一種方法,直接在父級聲明,但爲了區分原生事件和自定義事件,要用到事件修飾符 .native
,因此上面的示例也能夠這樣寫:
<i-button @click.native="handleClick"></i-button>
若是不寫 .native
修飾符,那上面的 @click
就是自定義事件 click,而非原生事件 click,但咱們在組件內只觸發了 on-click
事件,而不是 click
,因此直接寫 @click
會監聽不到。
通常來講,組件能夠有如下幾種關係:
A 和 B、B 和 C、B 和 D 都是父子關係,C 和 D 是兄弟關係,A 和 C 是隔代關係(可能隔多代)。組件間常常會通訊,Vue.js 內置的通訊手段通常有兩種:
ref
:給元素或組件註冊引用信息;
$parent
/ $children
:訪問父 / 子實例。
這兩種都是直接獲得組件實例,使用後能夠直接調用組件的方法或訪問數據,好比下面的示例中,用 ref 來訪問組件(部分代碼省略):
// component-a export default { data () { return { title: 'Vue.js' } }, methods: { sayHello () { window.alert('Hello'); } } }
<template> <component-a ref="comA"></component-a> </template> <script> export default { mounted () { const comA = this.$refs.comA; console.log(comA.title); // Vue.js comA.sayHello(); // 彈窗 } } </script>
$parent
和 $children
相似,也是基於當前上下文訪問父組件或所有子組件的。
這兩種方法的弊端是,沒法在跨級或兄弟間通訊,好比下面的結構:
// parent.vue <component-a></component-a> <component-b></component-b> <component-b></component-b>
咱們想在 component-a 中,訪問到引用它的頁面中(這裏就是 parent.vue)的兩個 component-b 組件,那這種狀況下,就得配置額外的插件或工具了,好比 Vuex 和 Bus 的解決方案
基於 Vue.js 開發獨立組件,並非新奇的挑戰,坦率地講,它本質上仍是 JavaScript。掌握了 Vue.js 組件的這三個 API 後,剩下的即是程序的設計。在組件開發中,最難的環節應當是解耦組件的交互邏輯,儘可能把複雜的邏輯分發到不一樣的子組件中,而後彼此創建聯繫,在這其中,計算屬性(computed)和混合(mixins)是兩個重要的技術點,合理利用,就能發揮出 Vue.js 語言的最大特色:把狀態(數據)的維護交給 Vue.js 處理,咱們只專一在交互上。