從小程序基礎(chǔ)庫(kù)版本 1.6.3 開(kāi)始,小程序支持簡(jiǎn)潔的組件化編程。查看自己使用的小程序基礎(chǔ)庫(kù)版本,可以通過(guò)在開(kāi)發(fā)者工具右側(cè)點(diǎn)擊詳情查看:
小程序的組件,其實(shí)就是一個(gè)目錄,該目錄需要包含4個(gè)文件:
首先需要在 json 文件中進(jìn)行自定義組件聲明(將 component 字段設(shè)為 true 可這一組文件設(shè)為自定義組件)
{
"component": true
}
其次,在要引入組件的頁(yè)面的json文件內(nèi),進(jìn)行引用聲明
{
"usingComponents": {
//自定義的組件名稱 : 組件路徑,注意是相對(duì)路徑,不能是絕對(duì)路徑
"component-tag-name": "path/to/the/custom/component"
}
}
這樣,在主頁(yè)面就可以使用了。
相比于vue的組件引入,小程序的方案更簡(jiǎn)潔。vue組件引入是需要 import 之后,同時(shí)在 components 里面注冊(cè),而小程序的組件只需要在 .json 里面注冊(cè),就可以在 wxml 里面使用。
和vue 相同,小程序也有slot概念。
在組件模板中可以提供一個(gè) <slot> 節(jié)點(diǎn),用于承載組件引用時(shí)提供的子節(jié)點(diǎn)。
// 主頁(yè)面內(nèi),<addlike>是組件
<addlike item="item" my_properties="sssss">
<text>我是被slot插入的文本</text>
</addlike>
// addlike 組件
<view class="container">
<view>hello, 這里是組件</view>
<view>hello, {{my_properties}}</view>
<slot></slot>
</view>
// 渲染后
<view class="container">
<view>hello, 這里是組件</view>
<view>hello, {{my_properties}}</view>
<text>我是被slot插入的文本</text>
</view>
如果需要在組件內(nèi)使用多個(gè)slot, 需要在組件js中聲明啟用:
Component({
options: {
multipleSlots: true // 在組件定義時(shí)的選項(xiàng)中啟用多slot支持
},
properties: { /* ... */ },
methods: { /* ... */ }
})
使用:
// 主頁(yè)面
<addlike item="item" my_properties="sssss">
// 在普通的元素上加入 slot 屬性,指定slotname, 就可以變成子元素的slot了
<text slot="slot1">我是被slot1插入的文本</text>
<text slot="slot2">我是被slot2插入的文本</text>
</addlike>
// 子頁(yè)面
<view class="container">
<view>hello, 這里是組件</view>
<view>hello, {{my_properties}}</view>
<slot name="slot1"></slot>
<slot name="slot2"></slot>
</view>
剛才我們說(shuō)了,一個(gè)組件內(nèi)應(yīng)該包括js, wxml, wxss, json 四個(gè)文件。wxml 相當(dāng)于是 HTML,wxss 相當(dāng)于是 css, 那么js 里面應(yīng)該寫(xiě)什么呢?
微信官方提供的案例中:
Component({
behaviors: [],
properties: {
},
data: {}, // 私有數(shù)據(jù),可用于模版渲染
// 生命周期函數(shù),可以為函數(shù),或一個(gè)在methods段中定義的方法名
attached: function(){},
moved: function(){},
detached: function(){},
methods: {
onMyButtonTap: function(){
},
_myPrivateMethod: function(){
},
_propertyChange: function(newVal, oldVal) {
}
}
})
里面調(diào)用了一個(gè)Component構(gòu)造器。Component構(gòu)造器可用于定義組件,調(diào)用Component構(gòu)造器時(shí)可以指定組件的屬性、數(shù)據(jù)、方法等。具體 Component里面可以放什么東西,如下所示:

組件化必然要涉及到數(shù)據(jù)的通信,為了解決數(shù)據(jù)在組件間的維護(hù)問(wèn)題,vue, react,angular 有不同的解決方案。而小程序的解決方案則簡(jiǎn)潔很多。
properties相當(dāng)于vue的props,是傳入外部數(shù)據(jù)的入口。
// 主頁(yè)面使用組件
<a add_like="{{add_like}}">
</a>
// 組件a.js 內(nèi)
Component({
properties:{
add_like:{
type:Array,
value:[],
observer:function(){
}
}
}
})
注意: 傳入的數(shù)據(jù),不管是簡(jiǎn)單數(shù)據(jù)類型,還是引用類型,都如同值復(fù)制一樣(和紅寶書(shū)里面描述js函數(shù)參數(shù)傳入是值復(fù)制還不一樣,紅寶書(shū)里面的意思是:簡(jiǎn)單數(shù)據(jù)類型直接復(fù)制數(shù)值,引用類型復(fù)制引用,也就是說(shuō)在函數(shù)內(nèi)修改參數(shù)對(duì)象的屬性,會(huì)影響到函數(shù)外對(duì)象的屬性)。
如果是Vue的props, 則可以通過(guò) .sync 來(lái)同步,而在小程序子組件里面,調(diào)用this.setData()修改父組件內(nèi)的數(shù)據(jù),不會(huì)影響到父組件里面的數(shù)據(jù), 也就是說(shuō),子組件property的修改,仿佛和父組件沒(méi)有任何關(guān)系。那么,如果是在子組件內(nèi)修改父組件的數(shù)據(jù),甚至是修改兄弟組件內(nèi)的數(shù)據(jù),有沒(méi)有簡(jiǎn)單的方法呢?下面會(huì)有講到
和vue類似,組件間交互的主要形式是自定義事件。
組件通過(guò) this.triggerEvent() 觸發(fā)自定義事件,主頁(yè)面在組件上 bind:component_method="main_page_mehod" 來(lái)接收自定義事件。
其中,this.triggerEvent() 方法接收自定義事件名稱外,還接收兩個(gè)對(duì)象,eventDetail 和 eventOptions。
// 子組件觸發(fā)自定義事件
ontap () {
// 所有要帶到主頁(yè)面的數(shù)據(jù),都裝在eventDetail里面
var eventDetail = {
name:'sssssssss',
test:[1,2,3]
}
// 觸發(fā)事件的選項(xiàng) bubbles是否冒泡,composed是否可穿越組件邊界,capturePhase 是否有捕獲階段
var eventOption = {
composed: true
}
this.triggerEvent('click_btn', eventDetail, eventOption)
}
// 主頁(yè)面里面
main_page_ontap (eventDetail) {
console.log(eventDetail)
// eventDetail
// changedTouches
// currentTarget
// target
// type
// ……
// detail 哈哈,所有的子組件的數(shù)據(jù),都通過(guò)該參數(shù)的detail屬性暴露出來(lái)
}
和vue提出的vuex的解決方案不同,小程序的組件間的通訊簡(jiǎn)單小巧。你可以和主頁(yè)面與組件通訊一樣,使用自定義事件來(lái)進(jìn)行通訊,當(dāng)然更簡(jiǎn)單方便的方法,是使用小程序提供的relations.
relations 是Component 構(gòu)造函數(shù)中的一個(gè)屬性,只要兩個(gè)組件的relations 屬性產(chǎn)生關(guān)聯(lián),他們兩個(gè)之間就可以捕獲到對(duì)方,并且可以相互訪問(wèn),修改對(duì)方的屬性,如同修改自己的屬性一樣。
Component({
relations:{
'./path_to_b': { // './path_to_b'是對(duì)方組件的相對(duì)路徑
type: 'child', // type可選擇兩組:parent和child、ancestor和descendant
linked:function(target){ } // 鉤子函數(shù),在組件linked時(shí)候被調(diào)用 target是組件的實(shí)例,
linkChanged: function(target){}
unlinked: function(target){}
}
},
})
比如說(shuō),有兩個(gè)組件如代碼所示:
// 組件a slot 包含了組件b <a> <b></b> </a>
他們之間的關(guān)系如下圖所示:

兩個(gè)組件捕獲到對(duì)方組件的實(shí)例,是通過(guò) this.getRelationNodes('./path_to_a')方法。既然獲取到了對(duì)方組件的實(shí)例,那么就可以訪問(wèn)到對(duì)方組件上的data, 也可以設(shè)置對(duì)方組件上的data, 但是不能調(diào)用對(duì)方組件上的方法。
// 在a 組件中
Component({
relations:{
'./path_to_b': {
type: 'child',
linked:function(target){ } // target是組件b的實(shí)例,
linkChanged: function(target){}
unlinked: function(target){}
}
},
methods:{
test () {
var nodes = this.getRelationNodes('./path_to_b')
var component_b = nodes[0];
// 獲取到b組件的數(shù)據(jù)
console.log(component_b.data.name)
// 設(shè)置父組件的數(shù)據(jù)
// 這樣的設(shè)置是無(wú)效的
this.setData({
component_b.data.name:'ss'
})
// 需要調(diào)用對(duì)方組件的setData()方法來(lái)設(shè)置
component_b.setData({
name:'ss'
})
}
}
})
// 在b 組件里面
Component({
relations:{
'./path_to_a': { //注意!必須雙方組件都聲明relations屬性
type:'parent'
}
},
data: {
name: 'dudu'
}
})
注意:1. 主頁(yè)面使用組件的時(shí)候,不能有數(shù)字,比如說(shuō) <component_sub1> 或 <component_sub_1>,可以在主頁(yè)面的json 里面設(shè)置一個(gè)新名字
{
"usingComponents":{
"test_component_subb": "../../../components/test_component_sub2/test_component_sub2"
}
}
2. relations 里面的路徑,比如說(shuō)這里:
?
是對(duì)方組件真實(shí)的相對(duì)路徑,而不是組件間的邏輯路徑。
3. 如果relations 沒(méi)有關(guān)聯(lián),那么 this.getRelationNodes 是獲取不到對(duì)方組件的
4. 本組件無(wú)法獲取本組件的實(shí)例,使用this.getRelatonsNodes('./ path_to_self ') 會(huì)返回一個(gè)null
4. type 可以選擇的 parent 、 child 、 ancestor 、 descendant