组件生命周期,通常是我们业务逻辑开始的地方。 如果业务场景比较复杂,组件生命周期有不符合预期的表现时, 可能会导致一些诡异的业务bug,它们极难复现和修复。
组件 attached 生命周期执行次数
按照通常的理解,除moved/show/hide
等生命周期可能多次执行外,
严格意义上与组件加载相关的生命周期,如:created
、attached
、ready
等,
每个组件实例应该只执行一次。但是事实真的如此吗?
背景
这个问题的发现,源于我们在小程序的报错日志中,
收到大量类似Cannot redefine property: isComponent
的报错。
原因分析
通过变量名可以追溯到我们在代码中的定义方式为:
Component({
lifetimes: {
attached() {
Object.defineProperty(this, 'isComponent', {
enumerable: true,
get() { return true },
});
},
},
});
很容易理解,这种错误的起因在于试图给对象重新定义一个不可配置的属性, 具体可以查看MDN上的说明。
可是这个定义是写在attached
生命周期当中的,难道说,组件的attached
生命周期被触发了两次?
天呐,这怎么可能?
是的,就是这么神奇!
场景还原
该问题并不容易复现,但是通过不断删繁就简、抽丝剥茧,最终还是找到了问题的根源:
可以通过以下代码复现该场景,或者直接访问小程序代码片段。
页面
// page.js
Page({
data: {
showChild2: false,
},
onChild1Attached() {
this.setData({ showChild2: true });
},
});
<!-- page.wxml -->
<child1 bind:attached="onChild1Attached"></child1>
<child2 wx:if="{{ showChild2 }}"></child2>
子组件1
与页面一同渲染,并在attached
的时候,通过triggerEvent
,通知页面更新状态并渲染子组件2。
// child1.js
Component({
lifetimes: {
attached() {
this.triggerEvent('attached');
},
},
});
<!-- child1.wxml -->
<view>child1</view>
子组件2
执行了两次attached
生命周期,导致报错。
// child2.js
Component({
lifetimes: {
attached() {
Object.defineProperty(this, 'isComponent', {
enumerable: true,
get() { return true },
});
},
},
});
<!-- child2.wxml -->
<view>child2</view>
组件 ready 生命周期的执行时机
小程序官方文档没有明确给出组件生命周期的执行顺序, 不过通过打印日志我们可以很容易地发现:
- 在加载阶段,会依次执行:created -> attached -> ready
- 在卸载阶段,会依次执行:detached
所以,看起来这个顺序貌似应该是:created -> attached -> ready -> detached。 但是实际情况果真如此吗?
背景
有段时间,客服经常反馈,我们的小程序存在串数据的现象。 例如:A商家的直播展示了B商家的商品。
原因分析
串数据发生在多个场景,考虑到数据是通过消息推送到小程序端上的,
最终怀疑问题出在WebSocket
通信上。
在小程序端,我们封装了一个WebSocket
通信组件,核心逻辑如下:
// socket.js
Component({
lifetimes: {
ready() {
this.getSocketConfig().then(config => {
this.ws = wx.connectSocket(config);
this.ws.onMessage(msg => {
const data = JSON.parse(msg.data);
this.onReceiveMessage(data);
});
});
},
detached() {
this.ws && this.ws.close({});
},
},
methods: {
getSocketConfig() {
// 从服务器请求 socket 连接配置
return new Promise(() => {});
},
onReceiveMessage(data) {
event.emit('message', data);
},
},
});
简单说,就是在组件ready
时,初始化一个WebSocket
连接并监听消息推送,
然后在detached
阶段关闭连接。
看起来并没有什么问题,那么就只能从结果倒推可能不符合常理的情况了。
数据串了 -> WebSocket 消息串了 -> WebSocket 没有正常关闭 -> close有问题/detached未执行/ready在detached之后执行
场景还原
此处的实际业务逻辑较为复杂,因此只能通过简化的代码来验证。 通过不断试验,最终发现:
可以通过以下代码复现该场景,或者直接访问小程序代码片段。
页面
// page.js
Page({
data: {
showChild: true,
},
onLoad() {
this.setData({ showChild: false });
},
});
<!-- page.wxml -->
<child wx:if="{{ showChild }}" />
组件
组件未ready
的时候销毁组件,会先同步执行detached
,然后异步执行ready
。
// child.js
Component({
lifetimes: {
created() {
console.log('created');
},
attached() {
console.log('attached');
},
ready() {
console.log('ready');
},
detached() {
console.log('detached');
}
},
});
拓展
即便是将初始化的工作从ready
前置到attached
阶段,只要有异步操作,仍然可能存在detached
先于异步回调执行的情况。
因此,请不要完全信任在组件detached
阶段的销毁操作。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!