微信小程序組件生命周期的踩坑記錄
組件生命周期,通常是我們業(yè)務(wù)邏輯開始的地方。
如果業(yè)務(wù)場景比較復(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è)問題的發(fā)現(xiàn),源于我們在小程序的報(bào)錯(cuò)日志中,
收到大量類似Cannot redefine property: isComponent的報(bào)錯(cuò)。

原因分析
通過變量名可以追溯到我們在代碼中的定義方式為:
Component({
lifetimes: {
attached() {
Object.defineProperty(this, 'isComponent', {
enumerable: true,
get() { return true },
});
},
},
});
很容易理解,這種錯(cuò)誤的起因在于試圖給對象重新定義一個(gè)不可配置的屬性,
具體可以查看MDN上的說明。
可是這個(gè)定義是寫在attached生命周期當(dāng)中的,難道說,組件的attached生命周期被觸發(fā)了兩次?
天吶,這怎么可能?
是的,就是這么神奇!
場景還原
該問題并不容易復(fù)現(xiàn),但是通過不斷刪繁就簡、抽絲剝繭,最終還是找到了問題的根源:
在頁面onLoad之前,通過setData改變狀態(tài)觸發(fā)子組件渲染,該子組件的attached生命周期會(huì)被觸發(fā)兩次。

可以通過以下代碼復(fù)現(xiàn)該場景,或者直接訪問小程序代碼片段。
頁面
// 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的時(shí)候,通過triggerEvent,通知頁面更新狀態(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ī)
小程序官方文檔沒有明確給出組件生命周期的執(zhí)行順序,不過通過打印日志我們可以很容易地發(fā)現(xiàn):
- 在加載階段,會(huì)依次執(zhí)行:created -> attached -> ready
- 在卸載階段,會(huì)依次執(zhí)行:detached
所以,看起來這個(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è)場景,考慮到數(shù)據(jù)是通過消息推送到小程序端上的,最終懷疑問題出在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ù)器請求 socket 連接配置
return new Promise(() => {});
},
onReceiveMessage(data) {
event.emit('message', data);
},
},
});
簡單說,就是在組件ready時(shí),初始化一個(gè)WebSocket連接并監(jiān)聽消息推送,然后在detached階段關(guān)閉連接。
看起來并沒有什么問題,那么就只能從結(jié)果倒推可能不符合常理的情況了。
數(shù)據(jù)串了 -> WebSocket 消息串了 -> WebSocket 沒有正常關(guān)閉 -> close有問題/detached未執(zhí)行/ready在detached之后執(zhí)行
場景還原
此處的實(shí)際業(yè)務(wù)邏輯較為復(fù)雜,因此只能通過簡化的代碼來驗(yàn)證。
通過不斷試驗(yàn),最終發(fā)現(xiàn):
組件的 ready 與 detached 執(zhí)行順序并沒有明確的先后關(guān)系。

可以通過以下代碼復(fù)現(xiàn)該場景,或者直接訪問小程序代碼片段。
頁面
// page.js
Page({
data: {
showChild: true,
},
onLoad() {
this.setData({ showChild: false });
},
});
<!-- page.wxml -->
<child wx:if="{{ showChild }}" />
組件
組件未ready的時(shí)候銷毀組件,會(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í)行的情況。
因此,請不要完全信任在組件detached階段的銷毀操作。
總結(jié)
到此這篇關(guān)于微信小程序組件生命周期踩坑的文章就介紹到這了,更多相關(guān)小程序組件生命周期內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
基于JS代碼實(shí)現(xiàn)圖片在頁面中旋轉(zhuǎn)效果
這篇文章主要介紹了基于JS代碼實(shí)現(xiàn)圖片在頁面中旋轉(zhuǎn)效果 的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-06-06
微信小程序?qū)崙?zhàn)之自定義模態(tài)彈窗(8)
這篇文章主要為大家詳細(xì)介紹了微信小程序?qū)崙?zhàn)之自定義模態(tài)彈窗,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-04-04
JavaScript實(shí)現(xiàn)的背景自動(dòng)變色代碼
這篇文章主要介紹了JavaScript實(shí)現(xiàn)的背景自動(dòng)變色代碼,涉及JavaScript數(shù)組操作結(jié)合定時(shí)函數(shù)實(shí)現(xiàn)修改頁面元素樣式的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-10-10
改變checkbox默認(rèn)選中狀態(tài)及取值的實(shí)現(xiàn)代碼
下面小編就為大家?guī)硪黄淖僣heckbox默認(rèn)選中狀態(tài)及取值的實(shí)現(xiàn)代碼。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-05-05
javascript放大鏡效果的簡單實(shí)現(xiàn)
這篇文章主要是對javascript放大鏡效果的簡單實(shí)現(xiàn)進(jìn)行了介紹,需要的朋友可以過來參考下,希望對大家有所幫助2013-12-12
微信小程序?qū)崿F(xiàn)MUI數(shù)字輸入框效果
這篇文章主要為大家詳細(xì)介紹了微信小程序?qū)崿F(xiàn)MUI數(shù)字輸入框效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-01-01
ES6中Promise的使用方法實(shí)例總結(jié)
這篇文章主要介紹了ES6中Promise的使用方法,結(jié)合實(shí)例形式總結(jié)分析了Promise對象中的各種常用方法及基本使用技巧,需要的朋友可以參考下2020-02-02
javascript中的document.open()方法使用介紹
document.open()方法打開一個(gè)新的文檔并用document.write()方法編寫文檔的內(nèi)容,下面有個(gè)不錯(cuò)的示例,大家可以感受下2013-10-10

