微信小程序組件生命周期的踩坑記錄
組件生命周期,通常是我們業(yè)務(wù)邏輯開(kāi)始的地方。
如果業(yè)務(wù)場(chǎng)景比較復(fù)雜,組件生命周期有不符合預(yù)期的表現(xiàn)時(shí),
可能會(huì)導(dǎo)致一些詭異的業(yè)務(wù)bug,它們極難復(fù)現(xiàn)和修復(fù)。
組件 attached 生命周期執(zhí)行次數(shù)
按照通常的理解,除moved/show/hide等生命周期可能多次執(zhí)行外,
嚴(yán)格意義上與組件加載相關(guān)的生命周期,如:created、attached、ready等,每個(gè)組件實(shí)例應(yīng)該只執(zhí)行一次。但是事實(shí)真的如此嗎?
背景
這個(gè)問(wèn)題的發(fā)現(xiàn),源于我們?cè)谛〕绦虻膱?bào)錯(cuò)日志中,
收到大量類(lèi)似Cannot redefine property: isComponent的報(bào)錯(cuò)。
原因分析
通過(guò)變量名可以追溯到我們?cè)诖a中的定義方式為:
Component({ lifetimes: { attached() { Object.defineProperty(this, 'isComponent', { enumerable: true, get() { return true }, }); }, }, });
很容易理解,這種錯(cuò)誤的起因在于試圖給對(duì)象重新定義一個(gè)不可配置的屬性,
具體可以查看MDN上的說(shuō)明。
可是這個(gè)定義是寫(xiě)在attached生命周期當(dāng)中的,難道說(shuō),組件的attached生命周期被觸發(fā)了兩次?
天吶,這怎么可能?
是的,就是這么神奇!
場(chǎng)景還原
該問(wèn)題并不容易復(fù)現(xiàn),但是通過(guò)不斷刪繁就簡(jiǎn)、抽絲剝繭,最終還是找到了問(wèn)題的根源:
在頁(yè)面onLoad之前,通過(guò)setData改變狀態(tài)觸發(fā)子組件渲染,該子組件的attached生命周期會(huì)被觸發(fā)兩次。
可以通過(guò)以下代碼復(fù)現(xiàn)該場(chǎng)景,或者直接訪問(wèn)小程序代碼片段。
頁(yè)面
// page.js Page({ data: { showChild2: false, }, onChild1Attached() { this.setData({ showChild2: true }); }, });
<!-- page.wxml --> <child1 bind:attached="onChild1Attached"></child1> <child2 wx:if="{{ showChild2 }}"></child2>
子組件1
與頁(yè)面一同渲染,并在attached的時(shí)候,通過(guò)triggerEvent,通知頁(yè)面更新?tīng)顟B(tài)并渲染子組件2。
// child1.js Component({ lifetimes: { attached() { this.triggerEvent('attached'); }, }, });
<!-- child1.wxml --> <view>child1</view>
子組件2
執(zhí)行了兩次attached生命周期,導(dǎo)致報(bào)錯(cuò)。
// child2.js Component({ lifetimes: { attached() { Object.defineProperty(this, 'isComponent', { enumerable: true, get() { return true }, }); }, }, });
<!-- child2.wxml --> <view>child2</view>
組件 ready 生命周期的執(zhí)行時(shí)機(jī)
小程序官方文檔沒(méi)有明確給出組件生命周期的執(zhí)行順序,不過(guò)通過(guò)打印日志我們可以很容易地發(fā)現(xiàn):
- 在加載階段,會(huì)依次執(zhí)行:created -> attached -> ready
- 在卸載階段,會(huì)依次執(zhí)行:detached
所以,看起來(lái)這個(gè)順序貌似應(yīng)該是:created -> attached -> ready -> detached。
但是實(shí)際情況果真如此嗎?
背景
有段時(shí)間,客服經(jīng)常反饋,我們的小程序存在串?dāng)?shù)據(jù)的現(xiàn)象。
例如:A商家的直播展示了B商家的商品。
原因分析
串?dāng)?shù)據(jù)發(fā)生在多個(gè)場(chǎng)景,考慮到數(shù)據(jù)是通過(guò)消息推送到小程序端上的,最終懷疑問(wèn)題出在WebSocket通信上。
在小程序端,我們封裝了一個(gè)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() { // 從服務(wù)器請(qǐng)求 socket 連接配置 return new Promise(() => {}); }, onReceiveMessage(data) { event.emit('message', data); }, }, });
簡(jiǎn)單說(shuō),就是在組件ready時(shí),初始化一個(gè)WebSocket連接并監(jiān)聽(tīng)消息推送,然后在detached階段關(guān)閉連接。
看起來(lái)并沒(méi)有什么問(wèn)題,那么就只能從結(jié)果倒推可能不符合常理的情況了。
數(shù)據(jù)串了 -> WebSocket 消息串了 -> WebSocket 沒(méi)有正常關(guān)閉 -> close有問(wèn)題/detached未執(zhí)行/ready在detached之后執(zhí)行
場(chǎng)景還原
此處的實(shí)際業(yè)務(wù)邏輯較為復(fù)雜,因此只能通過(guò)簡(jiǎn)化的代碼來(lái)驗(yàn)證。
通過(guò)不斷試驗(yàn),最終發(fā)現(xiàn):
組件的 ready 與 detached 執(zhí)行順序并沒(méi)有明確的先后關(guān)系。
可以通過(guò)以下代碼復(fù)現(xiàn)該場(chǎng)景,或者直接訪問(wèn)小程序代碼片段。
頁(yè)面
// page.js Page({ data: { showChild: true, }, onLoad() { this.setData({ showChild: false }); }, });
<!-- page.wxml --> <child wx:if="{{ showChild }}" />
組件
組件未ready的時(shí)候銷(xiāo)毀組件,會(huì)先同步執(zhí)行detached,然后異步執(zhí)行ready。
// child.js Component({ lifetimes: { created() { console.log('created'); }, attached() { console.log('attached'); }, ready() { console.log('ready'); }, detached() { console.log('detached'); } }, });
拓展
即便是將初始化的工作從ready前置到attached階段,只要有異步操作,仍然可能存在detached先于異步回調(diào)執(zhí)行的情況。
因此,請(qǐng)不要完全信任在組件detached階段的銷(xiāo)毀操作。
總結(jié)
到此這篇關(guān)于微信小程序組件生命周期踩坑的文章就介紹到這了,更多相關(guān)小程序組件生命周期內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript talbe表中指定位置插入一行的實(shí)現(xiàn)代碼 腳本之家修正版
用js實(shí)現(xiàn)的在table中指定的位置插入一行,先點(diǎn)一下表中你想插入的位置,點(diǎn)擊即可。2009-06-06JS實(shí)現(xiàn)向iframe中表單傳值的方法
這篇文章主要介紹了JS實(shí)現(xiàn)向iframe中表單傳值的方法,涉及js針對(duì)頁(yè)面元素及表單屬性操作相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2017-03-03Javascript面象對(duì)象成員、共享成員變量實(shí)驗(yàn)
Javascript 面象對(duì)象成員、共享成員變量實(shí)驗(yàn),需要的朋友可以參考下。2010-11-11原生JS實(shí)現(xiàn)-星級(jí)評(píng)分系統(tǒng)的簡(jiǎn)單實(shí)例
下面小編就為大家?guī)?lái)一篇原生JS實(shí)現(xiàn)-星級(jí)評(píng)分系統(tǒng)的簡(jiǎn)單實(shí)例。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-08-08微信小程序使用uni-app開(kāi)發(fā)小程序及部分功能實(shí)現(xiàn)詳解
uni-app是一個(gè)使用Vue.js 開(kāi)發(fā)所有前端應(yīng)用的框架,下面這篇文章主要給大家介紹了關(guān)于微信小程序使用uni-app開(kāi)發(fā)小程序及部分功能實(shí)現(xiàn)的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-08-08js實(shí)現(xiàn)遍歷含有input的table實(shí)例
這篇文章主要介紹了js實(shí)現(xiàn)遍歷含有input的table方法,結(jié)合實(shí)例形式分析了jsp讀取數(shù)據(jù)庫(kù)動(dòng)態(tài)生成table及JavaScript遍歷table的相關(guān)技巧,需要的朋友可以參考下2015-12-12js遍歷詳解(forEach, map, for, for...in, for...of)
在本篇文章里小編給大家整理的是關(guān)于js中的各種遍歷(forEach, map, for, for...in, for...of)相關(guān)知識(shí)點(diǎn)用法總結(jié),需要的朋友們參考下。2019-08-08JavaScript數(shù)組常用方法解析及數(shù)組扁平化
這篇文章主要介紹了JavaScript數(shù)組常用方法解析及數(shù)組扁平化,數(shù)組作為在開(kāi)發(fā)中常用的集合,除了for循環(huán)遍歷以外,還有很多內(nèi)置對(duì)象的方法,包括map,以及數(shù)組篩選元素filter等2022-07-07