一個再複雜的組件,都是由三部分組成的:prop、event、slot,它們構成了 Vue.js 組件的 API。若是你開發的是一個通用組件,那必定要事先設計好這三部分,由於組件一旦發佈,後面再修改 API 就很困難了,使用者都是但願不斷新增功能,修復 bug,而不是常常變動接口。若是你閱讀別人寫的組件,也能夠從這三個部分展開,它們能夠幫助你快速瞭解一個組件的全部功能。html
prop
定義了這個組件有哪些可配置的屬性,組件的核心功能也都是它來肯定的。寫通用組件時,props 最好用對象的寫法,這樣能夠針對每一個屬性設置類型、默認值或自定義校驗屬性的值,這點在組件開發中很重要,然而不少人卻忽視,直接使用 props 的數組用法,這樣的組件每每是不嚴謹的。好比咱們封裝一個按鈕組件 <i-button>
:vue
<template>
<button :class="'i-button-size' + size" :disabled="disabled"></button>
</template>
<script>
// 判斷參數是不是其中之一
function oneOf (value, validList) {
for (let i = 0; i < validList.length; i++) {
if (value === validList[i]) {
return true;
}
}
return false;
}
export default {
props: {
size: {
validator (value) {
return oneOf(value, ['small', 'large', 'default']);
},
default: 'default'
},
disabled: {
type: Boolean,
default: false
}
}
}
</script>
複製代碼
使用組件:數組
<i-button size="large"></i-button>
<i-button disabled></i-button>
複製代碼
組件中定義了兩個屬性:尺寸 size 和 是否禁用 disabled。其中 size 使用 validator
進行了值的自定義驗證,也就是說,從父級傳入的 size,它的值必須是指定的 small、large、default 中的一個,默認值是 default,若是傳入這三個之外的值,都會拋出一條警告。bash
要注意的是,組件裏定義的 props,都是單向數據流,也就是隻能經過父級修改,組件本身不能修改 props 的值,只能修改定義在 data 裏的數據,非要修改,也是經過後面介紹的自定義事件通知父級,由父級來修改。工具
在使用組件時,也能夠傳入一些標準的 html 特性,好比 id、class:ui
<i-button id="btn1" class="btn-submit"></i-button>
複製代碼
這樣的 html 特性,在組件內的 <button>
元素上會繼承,並不須要在 props 裏再定義一遍。這個特性是默認支持的,若是不指望開啓,在組件選項裏配置 inheritAttrs: false
就能夠禁用了。this
若是要給上面的按鈕組件 <i-button>
添加一些文字內容,就要用到組件的第二個 API:插槽 slot,它能夠分發組件的內容,好比在上面的按鈕組件中定義一個插槽:spa
<template>
<button :class="'i-button-size' + size" :disabled="disabled">
<slot></slot>
</button>
</template>
複製代碼
這裏的 <slot>
節點就是指定的一個插槽的位置,這樣在組件內部就能夠擴展內容了:插件
<i-button>按鈕 1</i-button>
<i-button>
<strong>按鈕 2</strong>
</i-button>
複製代碼
當須要多個插槽時,會用到具名 slot,好比上面的組件咱們再增長一個 slot,用於設置另外一個圖標組件:設計
<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 的解決方案。不過,它們都是依賴第三方插件的存在,這在開發獨立組件時是不可取的...
在組件開發中,最難的環節應當是解耦組件的交互邏輯,儘可能把複雜的邏輯分發到不一樣的子組件中,而後彼此創建聯繫,在這其中,計算屬性(computed)和混合(mixins)是兩個重要的技術點,合理利用,就能發揮出 Vue最大特色,把狀態(數據)的維護交給 Vue.js 處理,咱們只專一在交互上。