如果業務場景比較復雜,組件生命周期有不符合預期的表現時,
可能會導致一些詭異的業務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階段的銷毀操作。
總結
到此這篇關于微信小程序組件生命周期踩坑的文章就介紹到這了,更多相關小程序組件生命周期內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!
原文鏈接:https://juejin.cn/post/6934971793262247944