本篇来探索小程序自定义组件的事件系统, 由浅入深的探索事件冒泡 (bubbles), 事件穿越组件边界 (composed) 等问题.
最基础的自定义组件事件
为了探索冒泡, composed 等问题, 我们至少需要两个自定义组件.
假设我们有一个 tree-render 组件可以用来渲染一个树状数据.
tree-render 组件内部会调用一个 tree-node 组件.
然后在页面中引入 tree-render 组件.
graph LR
页面 --> 2[tree-render 组件] --> 3[tree-node 组件]
以下是树状数据
// treeData
{
type: 'root',
children: [
{
type: 'text', value: '这是一段文本'
}
]
}
<!-- tree-render 组件 -->
<view class='tree-render-body'>
<block wx:for="{{treeData.children}}" wx:key="index">
<tree-node node="{{item}}" bindmyclick="handleClick" />
</block>
</view>
<!-- tree-node 组件 -->
<block wx:if="{{node.type==='text'}}">
<text>{{node.value}}</text>
</block>
我们以这个例子为切入口, treeData 数据和 tree-node 组件会慢慢变复杂.
最基础的自定义组件事件就是在组件中 triggerEvent, 然后在使用组件的地方 bind 相应的事件.
<!-- tree-node 组件 -->
<block wx:if="{{node.type==='text'}}">
<text bindtap='handleTap'>{{node.value}}</text>
</block>
在 tree-node 组件中 triggerEvent. 默认情况下 bubbles 和 composed 都为 false.
// tree-node 组件
methods: {
handleTap(e) {
const { dataset } = e.currentTarget
this.triggerEvent('myclick', dataset)
}
}
我们在 tree-render 组件中放两个监听点, 并且在页面中也放两个监听点.
<!-- tree-render 组件 -->
<view class='tree-render-body' data-msg='监听于 tree-render 组件中的 view 节点' bindmyclick='log'>
<block wx:for="{{treeData.children}}" wx:key="index">
<tree-node node="{{item}}" data-msg='监听于 tree-render 组件中的 tree-node 节点' bindmyclick="log" />
</block>
</view>
<!-- 页面 组件 -->
<view data-msg='监听于页面中的 view 节点' bindmyclick='log'>
<table-render data-msg='监听于页面中的 tree-render 节点' bindmyclick='log' treeData="{{treeData}}" />
</view>
监听都准备好后点击文本来触发事件.
光想想都知道控制台只会打印出一个监听:
// log
监听于 tree-render 组件中的 tree-node 节点 tree-render.js:26
因为选项 bubbles 默认为 false, 也就是说事件不会冒泡, 所以只监听到一个监听, 这是理解的通的.
移除掉 tree-render 组件中绑定在 tree-node 节点上的监听事件
同样的条件下, 当我们移除掉 tree-render 组件中 tree-node 节点上的监听事件, 那么 tree-node 上层的 view 节点能够监听到吗?
一开始我的理解是能够监听到的. tree-node 节点没有监听, 那自然它上层的 view 节点能监听到, 监听到后再阻止事件冒泡.
很显然我把自定义组件中的冒泡跟常规事件里的 catch 混淆在一起了.
在小程序开发者工具中预览效果 - tree-node 节点移除监听事件
结果是什么都监听不到.
因此正确的理解是, 默认情况下自定义组件事件在冒到组件边界的时候就停止冒泡了.
除非开启 bubbles 为 true.
事件的冒泡选项为 true 时 (bubbles: true)
当事件的冒泡选项 bubbles 为 true 后, tree-node 节点以及其上层的 view 节点都能监听到事件了.
那么页面中能够监听到事件吗?
想想既然冒泡了应该能够监听到吧? 但是实际上是监听不到的.
监听于 tree-render 组件中的 tree-node 节点 tree-render.js:26
监听于 tree-render 组件中的 view 节点 tree-render.js:26
在小程序开发者工具中预览效果 - bubbles 为 true
现在终于能够理解小程序文档中的那句话了
事件只会冒到引用组件的节点树上!
除非开启 composed 为 true.
事件的 composed 选项为 true 时
最后我们把 composed 选项设为 true.
可以预见到页面中也能监听到事件了.
监听于 tree-render 组件中的 tree-node 节点 tree-render.js:26
监听于 tree-render 组件中的 view 节点 tree-render.js:26
监听于页面中的 tree-render 节点 VM127:18
监听于页面中的 view 节点 VM127:18
在小程序开发者工具中预览效果 - bubbles 和 composed 为 true
组件循环引用自身
之所以用树状结构来举例, 是因为树状结构节点的 children 又会套嵌 children.
也就是说 tree-node 组件可以调用自身.
<!-- tree-node 组件 -->
<block wx:if="{{node.type==='text'}}">
<text bindtap='handleTap'>{{node.value}}</text>
</block>
<block wx:elif="{{node.type === 'element'}}">
<view class="{{node.tagName}}">
<block wx:if="{{node.children}}">
<tree-node wx:for="{{node.children}}" wx:key="index" node="{{item}}" />
</block>
</view>
</block>
// tree-node.json
{
"component": true,
"usingComponents": {
"tree-node": "./tree-node"
}
}
更改 treeData
treeData: {
type: "root",
children: [
{
type: "element",
tagName: "ol",
children: [
{
type: "element",
tagName: "li",
children: [
{
type: "text",
value: "列表项1, 点击会触发事件",
},
],
},
{
type: "element",
tagName: "li",
children: [
{
type: "text",
value: "列表项2, 点击会触发事件",
},
],
},
],
},
],
},
正好借这个例子来验证一下对事件的理解.
graph LR
页面 --> 2[tree-render 组件] --> 3[tree-node 组件 ol] --> 4[tree-node 组件 li] --> 5[tree-node text]
当 tree-node 组件内引入自身时没有 bindmyclick
<!-- tree-node 组件引用自身处 -->
<block wx:if="{{node.children}}">
<tree-node wx:for="{{node.children}}" wx:key="index" node="{{item}}" />
</block>
| bubbles | composed | 结果 |
|---|---|---|
| false | false | 监听不到事件 |
| true | false | 监听不到事件 |
| true | true | 页面和 tree-render 都能监听到事件 |
当 tree-node 组件内引入自身时有 bindmyclick
<!-- tree-node 组件引用自身处 -->
<block wx:if="{{node.children}}">
<tree-node wx:for="{{node.children}}" wx:key="index" node="{{item}}" data-msg='监听于 tree-node 组件中的 {{node.tagName}} 节点' bindmyclick='log' />
</block>
| bubbles | composed | 结果 |
|---|---|---|
| false | false | 监听于 tree-node 组件中的 li 节点 |
| true | false | 监听于 tree-node 组件中的 li 节点 |
| true | true | 都能监听到事件 |
你理解对了吗? 打开小程序开发者工具看得更加清楚.