微信小程序组件生命周期的踩坑记录


Posted in Javascript onMarch 03, 2021

组件生命周期,通常是我们业务逻辑开始的地方。

如果业务场景比较复杂,组件生命周期有不符合预期的表现时,

可能会导致一些诡异的业务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生命周期被触发了两次?

天呐,这怎么可能?

是的,就是这么神奇!

场景还原

该问题并不容易复现,但是通过不断删繁就简、抽丝剥茧,最终还是找到了问题的根源:

在页面onLoad之前,通过setData改变状态触发子组件渲染,该子组件的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之后执行

场景还原

此处的实际业务逻辑较为复杂,因此只能通过简化的代码来验证。

通过不断试验,最终发现:

组件的 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阶段的销毁操作。

总结

到此这篇关于微信小程序组件生命周期踩坑的文章就介绍到这了,更多相关小程序组件生命周期内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Javascript 相关文章推荐
在UpdatePanel内jquery easyui效果失效的解决方法
Apr 11 Javascript
JavaScript高级程序设计(第3版)学习笔记 概述
Oct 11 Javascript
Javascript 遍历页面text控件详解
Jan 06 Javascript
jQuery中parents()和parent()的区别分析
Oct 28 Javascript
jQuery自动添加表单项的方法
Jul 13 Javascript
JS实现不规则TAB选项卡效果代码
Sep 16 Javascript
关于事件mouseover ,mouseout ,mouseenter,mouseleave的区别
Oct 12 Javascript
Jquery+Ajax+xml实现中国地区选择三级联动菜单效果(推荐)
Jun 09 jQuery
Vue之Watcher源码解析(2)
Jul 19 Javascript
微信小程序使用setData修改数组中单个对象的方法分析
Dec 30 Javascript
vue点击自增和求和的实例代码
Nov 06 Javascript
微信小程序实现时间进度条功能
Nov 17 Javascript
vite2.0+vue3移动端项目实战详解
Mar 03 #Vue.js
基于JavaScript实现简单的轮播图
Mar 03 #Javascript
js面向对象方式实现拖拽效果
Mar 03 #Javascript
Vue多选列表组件深入详解
Mar 02 #Vue.js
Vue2.x-使用防抖以及节流的示例
Mar 02 #Vue.js
Vue中避免滥用this去读取data中数据
Mar 02 #Vue.js
详解微信小程序(Taro)手动埋点和自动埋点的实现
Mar 02 #Javascript
You might like
php中选择什么接口(mysql、mysqli)访问mysql
2013/02/06 PHP
php计算整个mysql数据库大小的方法
2015/06/19 PHP
javascript之AJAX框架使用说明
2010/04/24 Javascript
jQuery动态设置form表单的enctype值(实现代码)
2013/07/04 Javascript
JavaScript中String.prototype用法实例
2015/05/20 Javascript
JAVA四种基本排序方法实例总结
2015/07/24 Javascript
jQuery实现的类似淘宝网站搜索框样式代码分享
2015/08/24 Javascript
深入浅出讲解ES6的解构
2016/08/03 Javascript
Bootstrap选项卡学习笔记分享
2017/02/13 Javascript
vue.js获取数据库数据实例代码
2017/05/26 Javascript
利用node.js制作命令行工具方法教程(一)
2017/06/22 Javascript
BootStrap导航栏问题记录
2017/07/31 Javascript
Angular js 实现添加用户、修改密码、敏感字、下拉菜单的综合操作方法
2017/10/24 Javascript
微信小程序switch开关选择器使用详解
2018/01/31 Javascript
[02:38]DOTA2亚洲邀请赛 IG战队巡礼
2015/02/03 DOTA
[44:41]Fnatic vs Liquid 2018国际邀请赛小组赛BO2 第二场 8.16
2018/08/17 DOTA
[06:57]DOTA2-DPC中国联赛 正赛 Ehome vs PSG.LGD 选手采访
2021/03/11 DOTA
php使用递归与迭代实现快速排序示例
2014/01/23 Python
python解析json串与正则匹配对比方法
2018/12/20 Python
Python简单获取二维数组行列数的方法示例
2018/12/21 Python
Python django搭建layui提交表单,表格,图标的实例
2019/11/18 Python
Django-Scrapy生成后端json接口的方法示例
2020/10/06 Python
详解Selenium 元素定位和WebDriver常用方法
2020/12/04 Python
意大利灯具购物网站:Lampade.it
2018/10/18 全球购物
C#面试问题
2016/07/29 面试题
使用C#编写创建一个线程的代码
2013/01/22 面试题
WebSphere面试题:在WebSphere里面如何部署一个应用
2015/08/02 面试题
计算机应用专业毕业生求职信
2013/10/24 职场文书
人力资源专员岗位职责
2014/01/30 职场文书
读书活动实施方案
2014/03/10 职场文书
中学生操行评语
2014/04/24 职场文书
2014年党员整改措施
2014/10/24 职场文书
2015年暑期社会实践总结
2015/07/13 职场文书
大学生如何逃脱“毕业季创业队即散伙”魔咒?
2019/08/19 职场文书
教你用Python爬取英雄联盟皮肤原画
2021/06/13 Python
使用Bandicam录制鼠标指针并附带点击声音,还可以添加点击动画效果
2022/04/11 数码科技