微信小程序组件化的解决方案(上)
发表时间:2021-3-31
发布人:葵宇科技
浏览次数:73
从小程序基础库版本 1.6.3 开始,小程序支持简洁的组件化编程。查看自己使用的小程序基础库版本,可以通过在开发者工具右侧点击详情查看:
最基本的组件
小程序的组件,其实就是一个目录,该目录需要包含4个文件:
- xxx.json
- xxx.wxml
- xxx.wxss
- xxx.js
声明一个组件
首先需要在 json
文件中进行自定义组件声明(将 component
字段设为 true
可这一组文件设为自定义组件)
{
"component": true
}
其次,在要引入组件的页面的json文件内,进行引用声明
{
"usingComponents": {
//自定义的组件名称 : 组件路径,注意是相对路径,不能是绝对路径
"component-tag-name": "path/to/the/custom/component"
}
}
这样,在主页面就可以使用了。
相比于vue的组件引入,小程序的方案更简洁。vue组件引入是需要 import 之后,同时在 components 里面注册,而小程序的组件只需要在 .json 里面注册,就可以在 wxml 里面使用。
使用slot
和vue 相同,小程序也有slot概念。
单一slot
在组件模板中可以提供一个 <slot>
节点,用于承载组件引用时提供的子节点。
// 主页面内,<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>
多个slot
如果需要在组件内使用多个slot, 需要在组件js中声明启用:
Component({
options: {
multipleSlots: true // 在组件定义时的选项中启用多slot支持
},
properties: { /* ... */ },
methods: { /* ... */ }
})
使用:
// 主页面
<addlike item="item" my_properties="sssss">
// 在普通的元素上加入 slot 属性,指定slotname, 就可以变成子元素的slot了
<text slot="slot1">我是被slot1插入的文本</text>
<text slot="slot2">我是被slot2插入的文本</text>
</addlike>
// 子页面
<view class="container">
<view>hello, 这里是组件</view>
<view>hello, {{my_properties}}</view>
<slot name="slot1"></slot>
<slot name="slot2"></slot>
</view>
Component构造器
刚才我们说了,一个组件内应该包括js, wxml, wxss, json 四个文件。wxml 相当于是 HTML,wxss 相当于是 css, 那么js 里面应该写什么呢?
微信官方提供的案例中:
Component({
behaviors: [],
properties: {
},
data: {}, // 私有数据,可用于模版渲染
// 生命周期函数,可以为函数,或一个在methods段中定义的方法名
attached: function(){},
moved: function(){},
detached: function(){},
methods: {
onMyButtonTap: function(){
},
_myPrivateMethod: function(){
},
_propertyChange: function(newVal, oldVal) {
}
}
})
里面调用了一个Component构造器。Component构造器可用于定义组件,调用Component构造器时可以指定组件的属性、数据、方法等。具体 Component里面可以放什么东西,如下所示:
组件与数据通信
组件化必然要涉及到数据的通信,为了解决数据在组件间的维护问题,vue, react,angular 有不同的解决方案。而小程序的解决方案则简洁很多。
主页面传入数据到组件
properties相当于vue的props,是传入外部数据的入口。
// 主页面使用组件
<a add_like="{{add_like}}">
</a>
// 组件a.js 内
Component({
properties:{
add_like:{
type:Array,
value:[],
observer:function(){
}
}
}
})
注意: 传入的数据,不管是简单数据类型,还是引用类型,都如同值复制一样(和红宝书里面描述js函数参数传入是值复制还不一样,红宝书里面的意思是:简单数据类型直接复制数值,引用类型复制引用,也就是说在函数内修改参数对象的属性,会影响到函数外对象的属性)。
如果是Vue的props, 则可以通过 .sync 来同步,而在小程序子组件里面,调用this.setData()修改父组件内的数据,不会影响到父组件里面的数据, 也就是说,子组件property的修改,仿佛和父组件没有任何关系。那么,如果是在子组件内修改父组件的数据,甚至是修改兄弟组件内的数据,有没有简单的方法呢?下面会有讲到
组件传出数据到主页面
和vue类似,组件间交互的主要形式是自定义事件。
组件通过 this.triggerEvent()
触发自定义事件,主页面在组件上 bind:component_method="main_page_mehod"
来接收自定义事件。
其中,this.triggerEvent()
方法接收自定义事件名称外,还接收两个对象,eventDetail
和 eventOptions
。
// 子组件触发自定义事件
ontap () {
// 所有要带到主页面的数据,都装在eventDetail里面
var eventDetail = {
name:'sssssssss',
test:[1,2,3]
}
// 触发事件的选项 bubbles是否冒泡,composed是否可穿越组件边界,capturePhase 是否有捕获阶段
var eventOption = {
composed: true
}
this.triggerEvent('click_btn', eventDetail, eventOption)
}
// 主页面里面
main_page_ontap (eventDetail) {
console.log(eventDetail)
// eventDetail
// changedTouches
// currentTarget
// target
// type
// ……
// detail 哈哈,所有的子组件的数据,都通过该参数的detail属性暴露出来
}
组件之间数据通信
和vue提出的vuex的解决方案不同,小程序的组件间的通讯简单小巧。你可以和主页面与组件通讯一样,使用自定义事件来进行通讯,当然更简单方便的方法,是使用小程序提供的relations.
relations 是Component 构造函数中的一个属性,只要两个组件的relations 属性产生关联,他们两个之间就可以捕获到对方,并且可以相互访问,修改对方的属性,如同修改自己的属性一样。
Component({
relations:{
'./path_to_b': { // './path_to_b'是对方组件的相对路径
type: 'child', // type可选择两组:parent和child、ancestor和descendant
linked:function(target){ } // 钩子函数,在组件linked时候被调用 target是组件的实例,
linkChanged: function(target){}
unlinked: function(target){}
}
},
})
比如说,有两个组件如代码所示:
// 组件a slot 包含了组件b
<a>
<b></b>
</a>
他们之间的关系如下图所示:
两个组件捕获到对方组件的实例,是通过 this.getRelationNodes('./path_to_a')方法。既然获取到了对方组件的实例,那么就可以访问到对方组件上的data, 也可以设置对方组件上的data, 但是不能调用对方组件上的方法。
// 在a 组件中
Component({
relations:{
'./path_to_b': {
type: 'child',
linked:function(target){ } // target是组件b的实例,
linkChanged: function(target){}
unlinked: function(target){}
}
},
methods:{
test () {
var nodes = this.getRelationNodes('./path_to_b')
var component_b = nodes[0];
// 获取到b组件的数据
console.log(component_b.data.name)
// 设置父组件的数据
// 这样的设置是无效的
this.setData({
component_b.data.name:'ss'
})
// 需要调用对方组件的setData()方法来设置
component_b.setData({
name:'ss'
})
}
}
})
// 在b 组件里面
Component({
relations:{
'./path_to_a': { //注意!必须双方组件都声明relations属性
type:'parent'
}
},
data: {
name: 'dudu'
}
})
注意:1. 主页面使用组件的时候,不能有数字,比如说 <component_sub1> 或 <component_sub_1>,可以在主页面的json 里面设置一个新名字
{
"usingComponents":{
"test_component_subb": "../../../components/test_component_sub2/test_component_sub2"
}
}
2. relations 里面的路径,比如说这里:
?
是对方组件真实的相对路径,而不是组件间的逻辑路径。
3. 如果relations 没有关联,那么 this.getRelationNodes 是获取不到对方组件的
4. 本组件无法获取本组件的实例,使用this.getRelatonsNodes('./ path_to_self ') 会返回一个null
4. type 可以选择的 parent
、 child
、 ancestor
、 descendant