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


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 相关文章推荐
无缝滚动改进版支持上下左右滚动(封装成函数)
Dec 04 Javascript
jQuery Pagination Ajax分页插件(分页切换时无刷新与延迟)中文翻译版
Jan 11 Javascript
window.location.href IE下跳转失效的解决方法
Mar 27 Javascript
jQuery学习笔记之jQuery构建函数的7种方法
Jun 03 Javascript
常用jQuery代码分享
Jul 14 Javascript
ECMA5数组的新增方法有哪些及forEach()模仿实现
Nov 03 Javascript
微信小程序 删除项目工程实现步骤
Nov 10 Javascript
JavaScript获取短信验证码(周期性)
Dec 29 Javascript
基于Node.js的WebSocket通信实现
Mar 11 Javascript
原生JavaScript实现精美的淘宝轮播图效果示例【附demo源码下载】
May 27 Javascript
express默认日志组件morgan的方法
Apr 05 Javascript
JavaScript实现的拼图算法分析
Feb 13 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字符转义相关函数小结(php下的转义字符串)
2007/04/12 PHP
php 中英文语言转换类
2011/09/07 PHP
PHP使用array_multisort对多个数组或多维数组进行排序
2014/12/16 PHP
php创建桌面快捷方式实现方法
2015/12/31 PHP
PHP检测数据类型的几种方法(总结)
2017/03/04 PHP
基于Laravel实现的用户动态模块开发
2017/09/21 PHP
Laravel 默认邮箱登录改成用户名登录的实现方法
2019/08/12 PHP
IE不出现Flash激活框的小发现的js实现方法
2007/09/07 Javascript
JavaScript prototype属性使用说明
2010/05/13 Javascript
jQuery基础知识filter()和find()实例说明
2010/07/06 Javascript
使用JavaScript构建JSON格式字符串实现步骤
2013/03/22 Javascript
Jquery通过Ajax访问XML数据的小例子
2013/11/18 Javascript
JavaScript实现Iterator模式实例分析
2015/06/09 Javascript
jQuery实现点击后标记当前菜单位置(背景高亮菜单)效果
2015/08/22 Javascript
Angular2中如何使用ngx-translate进行国际化
2017/05/21 Javascript
jquery 通过ajax请求获取后台数据显示在表格上的方法
2018/08/08 jQuery
微信小程序提取公用函数到util.js及使用方法示例
2019/01/10 Javascript
AjaxFileUpload.js实现异步上传文件功能
2019/04/19 Javascript
el-form 多层级表单的实现示例
2020/09/10 Javascript
用Pycharm实现鼠标滚轮控制字体大小的方法
2019/01/15 Python
pytorch实现对输入超过三通道的数据进行训练
2020/01/15 Python
Python列表解析操作实例总结
2020/02/26 Python
python 三种方法提取pdf中的图片
2021/02/07 Python
实例讲解CSS3中的box-flex弹性盒属性布局
2016/06/09 HTML / CSS
亚马逊印度站:Amazon.in
2017/10/15 全球购物
美国体育用品商店:Rally House(NCAA、NFL、MLB、NBA、NHL和MLS)
2018/01/03 全球购物
Molly Bracken法国电子商店:法国女性时尚品牌
2019/07/24 全球购物
KIKO MILANO俄罗斯官网:意大利领先的化妆品和护肤品品牌
2021/01/09 全球购物
Servlet如何得到客户端机器的信息
2014/10/17 面试题
医学专业毕业生推荐信
2014/07/12 职场文书
简易版租房协议书范本
2014/10/13 职场文书
群众路线个人整改方案
2014/10/25 职场文书
2015年公民道德宣传日活动总结
2015/03/23 职场文书
2019XX公司员工考核管理制度!
2019/08/07 职场文书
Python Pandas pandas.read_sql_query函数实例用法分析
2021/06/21 Python
JS封装cavans多种滤镜组件
2022/02/15 Javascript