錄:從WebView遷移的完整指南)
前幾天又在微信小程序里折騰Skyline模式本來只是想驗(yàn)證一個(gè)新頁面的滑動流暢度結(jié)果一個(gè)日期選擇器直接消失iPhone上頁面還滑不動。旁邊同事來了句讓你追新。我心里不服但確實(shí)被坑得夠嗆。花了兩三個(gè)晚上把問題逐個(gè)定位后我對Skyline的認(rèn)知也變了不少——它不是WebView模式的簡單加速而是換了一套渲染和組件運(yùn)行規(guī)則。這篇就記錄一下這些坑給正在切Skyline或打算試試的開發(fā)者做個(gè)參考。1. Skyline切換的第一課先把渲染引擎換代這件事想清楚1.1 它和WebView到底差在哪Skyline是微信小程序的新渲染引擎底層不再是WebView的那套DOM加CSS布局而是自己維護(hù)了一套渲染樹邏輯層和渲染層之間的數(shù)據(jù)交換走的是更直接的通道。說得通俗點(diǎn)傳統(tǒng)WebView模式里你的wxml最終變成DOM節(jié)點(diǎn)布局、樣式、滾動都交給瀏覽器內(nèi)核處理Skyline相當(dāng)于微信自己做了一個(gè)精簡的渲染層頁面元素由原生渲染引擎直接繪制動畫可以跑在獨(dú)立線程上所以滑動跟手度和復(fù)雜動畫的表現(xiàn)確實(shí)更好。但這里有個(gè)很常見的思維誤區(qū)很多人以為Skyline只是性能優(yōu)化版WebView切過去頁面看起來一樣就萬事大吉。實(shí)際完全不是。Skyline下的組件運(yùn)行機(jī)制、事件觸發(fā)順序、原生控件層級都和WebView有差異。比如你在網(wǎng)上搜到的微信小程序渲染機(jī)制特殊在真機(jī)上會體現(xiàn)得特別明顯同一個(gè)組件開發(fā)者工具里正常一上iOS就行為不同。這種開發(fā)環(huán)境正常、真機(jī)異常的割裂感在Skyline模式下會被放得更大。1.2 開啟Skyline的正確方式與最小配置先說我建議的最小開啟方式基礎(chǔ)庫選3.0以上。關(guān)于基礎(chǔ)庫版本從哪設(shè)置開發(fā)者工具右上角詳情-本地設(shè)置里可以切換調(diào)試基礎(chǔ)庫真機(jī)預(yù)覽則依賴用戶微信版本和后臺設(shè)置的最低基礎(chǔ)庫版本。我建議最低版本設(shè)置為2.30.4以上避免老用戶直接進(jìn)空頁面。app.json中配置renderer: skyline也可以在單個(gè)頁面的json里寫renderer: skyline實(shí)現(xiàn)按頁面開啟。官方還推薦配合lazyCodeLoading: requiredComponents做按需注入但這一步后面單獨(dú)說因?yàn)樗旧砭褪莻€(gè)坑。有些人開了全局Skyline后發(fā)現(xiàn)某個(gè)頁面白屏或組件不顯示第一反應(yīng)是全部回滾。其實(shí)更穩(wěn)妥的做法是按頁面灰度先挑兩三個(gè)不依賴復(fù)雜原生組件的頁面開啟Skyline跑一周真機(jī)自測再逐步放大范圍。下面這張表是我自己整理的對比方便你判斷哪些頁面風(fēng)險(xiǎn)高對比項(xiàng)WebView渲染Skyline渲染布局內(nèi)核瀏覽器內(nèi)核DOM布局自繪渲染樹動畫線程JS線程/合成線程獨(dú)立渲染線程scroll-view滾動依賴內(nèi)核滾動增強(qiáng)滾動需適配寫法原生組件層級cover-view規(guī)則復(fù)雜原生組件與普通組件同層兼容性所有基礎(chǔ)庫基礎(chǔ)庫2.30部分能力有差異彈層定位相對最近定位祖先受滾動容器影響較大這張表不是讓你背參數(shù)而是想說切換前先對著自己的頁面清單過一遍哪些用到了原生組件、哪些依賴scroll-view、哪些有復(fù)雜的彈層定位這些就是第一批容易出問題的頁面。2. 滾動與彈層我在真機(jī)上最先翻車的兩個(gè)場景2.1 iPhone上頁面突然滑不動現(xiàn)象描述開發(fā)者工具里一切正常安卓真機(jī)也正常但iPhone 12和iPhone 14兩臺真機(jī)預(yù)覽時(shí)頁面底部內(nèi)容滾動不了手指滑動時(shí)整個(gè)頁面紋絲不動偶爾還能觸發(fā)下拉刷新。一開始我以為是樣式問題。檢查發(fā)現(xiàn)頁面最外層不是page而是一個(gè)自定義容器內(nèi)部高度用了100vh下面再放scroll-view。按照WebView的慣性scroll-view內(nèi)容超長就該能滾但Skyline下scroll-view的默認(rèn)行為變了它不再自動把超出部分變成可滾動區(qū)域必須顯式給滾動容器一個(gè)確定高度或者用Skyline的增強(qiáng)滾動能力。排查過程我大概花了40分鐘去掉外層overflow: hidden無效給scroll-view加height: calc(100vh - 頂部高度)部分生效但底部仍卡在iPhone上打開調(diào)試面板看到scroll-view的滾動高度為0也就是內(nèi)容高度沒有被正確計(jì)算最終方案是把滾動容器換成page自帶的滾動頁面結(jié)構(gòu)改成普通流式布局讓頁面級滾動接管如果必須用scroll-view則開啟增強(qiáng)滾動并在容器上顯式設(shè)置flex: 1且外層使用flex布局。這個(gè)坑背后的原因是Skyline模式下頁面滾動容器和WebView的無限高文檔加overflow滾動模型不同容器高度需要明確參與布局計(jì)算。這也解釋了為什么網(wǎng)上會有那么多蘋果手機(jī)在微信小程序不能進(jìn)行滑動滾動的帖子——一半是歷史iOS bug另一半是在Skyline下把scroll-view當(dāng)WebView用。2.2 日期選擇器在scroll-view里消失第二個(gè)場景更詭異頁面里用了一個(gè)時(shí)間選擇器組件我用的是uni-datetime-picker這類跨端組件在普通項(xiàng)目里很穩(wěn)頁面外層套著scroll-view。切到Skyline后點(diǎn)擊選擇器彈層要么不出現(xiàn)要么出現(xiàn)在屏幕左上角要么一閃而過。我一開始以為組件庫不兼容準(zhǔn)備換掉。后來用微信開發(fā)者工具的Skyline調(diào)試器看節(jié)點(diǎn)發(fā)現(xiàn)彈層被渲染到了scroll-view的滾動上下文內(nèi)部定位參考系在Skyline下變成了滾動容器而WebView下彈層默認(rèn)找最近的定位祖先行為不一樣。也就是說問題不在組件本身而在于彈層掛載位置。解決方案優(yōu)先把選擇器、彈層這類組件放到頁面根節(jié)點(diǎn)不要包在scroll-view內(nèi)部如果組件庫支持掛載節(jié)點(diǎn)配置設(shè)置掛載到page或根節(jié)點(diǎn)實(shí)在不行就用popup類組件替代這類組件通常監(jiān)聽頁面滾動并固定彈層位置適配性好很多。這個(gè)坑的通用結(jié)論是在Skyline模式下凡是彈層加滾動容器的組合都要重新審視。不僅僅是日期選擇器包括下拉菜單、篩選面板、分享彈窗只要內(nèi)部有absolute或fixed定位的浮層都要考慮滾動上下文變化。3. 組件方法與數(shù)據(jù)更新幾個(gè)看似毫無關(guān)聯(lián)的報(bào)錯3.1 does not have a method方法去哪了有段時(shí)間控制臺老是報(bào)Component pages/index/index does not have a method navigatorcl。當(dāng)時(shí)頁面里有個(gè)自定義組件我在父頁面通過selectComponent拿到實(shí)例后調(diào)用了一個(gè)方法。報(bào)錯信息明確說該方法沒定義。我檢查組件代碼方法明明寫在methods里。后來發(fā)現(xiàn)頁面onLoad里就立即調(diào)了selectComponent在Skyline下組件實(shí)例可能還沒完成掛載拿到的是舊實(shí)例或不完整實(shí)例方法自然找不到。WebView下因?yàn)殇秩竞瓦壿嬍峭粋€(gè)線程排隊(duì)執(zhí)行通常onReady之后再調(diào)就沒事Skyline下渲染和邏輯線程分離組件掛載完成時(shí)機(jī)更晚。解決套路在onReady或setTimeout(0)后再取組件實(shí)例如果你必須在onLoad里傳數(shù)據(jù)給組件優(yōu)先通過properties/data初始值傳入不要依賴實(shí)例方法方法名大小寫也順手核對一遍Skyline報(bào)錯對大小寫敏感差一個(gè)字符就是另一個(gè)報(bào)錯。還有一種情況是組件被lazyCodeLoading按需注入后頁面onLoad時(shí)組件代碼還沒下載完。這個(gè)問題在下面單獨(dú)細(xì)說。3.2 setData路徑賦值在Skyline下更容易踩空網(wǎng)上有個(gè)很常見的寫法是this.setData({ userinfo.nickname: that.data.nickname })用點(diǎn)號路徑去更新嵌套字段。這種寫法在WebView里能用但用起來要注意如果userinfo一開始沒定義或者路徑中間某個(gè)節(jié)點(diǎn)是undefinedsetData會靜默失敗頁面不更新連報(bào)錯都沒有。在Skyline下數(shù)據(jù)從邏輯層同步到渲染層的通道變了對路徑解析更嚴(yán)格我遇到過幾次key路徑不合法導(dǎo)致整次setData不生效的情況排查起來非常費(fèi)勁。我的建議是盡量不用路徑字符串拼setData尤其不要動態(tài)拼接像this.setData({ [userinfo. key]: value })這種寫法在WebView下偶爾能用Skyline下可能就是隱患。改成先把數(shù)據(jù)對象整體構(gòu)造好一次性setDataconst nextData { ...this.data.userinfo, nickname: that.data.nickname } this.setData({ userinfo: nextData })這樣數(shù)據(jù)路徑固定diff也高效踩坑概率小很多。3.3 lazyCodeLoading開啟后首屏別急著調(diào)組件lazyCodeLoading: requiredComponents的本意是按需注入組件代碼減少首包體積。但如果你在頁面onLoad里馬上調(diào)用某個(gè)自定義組件的方法或者期望組件已經(jīng)渲染完成就會遇到組件代碼還沒注入完畢的情況。這其實(shí)和3.1是同一個(gè)問題只是觸發(fā)源不同。我的建議開啟lazyCodeLoading后關(guān)鍵組件方法調(diào)用放到onReady里并加一個(gè)存在性判斷const instance this.selectComponent(#my-component) if (instance typeof instance.someMethod function) { instance.someMethod() }不要假設(shè)組件一定存在更不要在一個(gè)組件的方法里直接調(diào)另一個(gè)還沒渲染的組件的方法。團(tuán)隊(duì)里有人為了圖省事在onLoad里鏈?zhǔn)秸{(diào)了三個(gè)組件的方法結(jié)果首屏偶發(fā)白屏后來全部改成onReady加判斷才穩(wěn)定下來。4. 導(dǎo)航欄、Canvas與文件路徑容易被業(yè)務(wù)代碼掩蓋的坑4.1 自定義導(dǎo)航欄高度獲取時(shí)機(jī)比你想的更講究做自定義導(dǎo)航欄時(shí)常規(guī)代碼是const { statusBarHeight } wx.getWindowInfo() const menu wx.getMenuButtonBoundingClientRect() const navBarHeight (menu.top - statusBarHeight) * 2 menu.height這套代碼在WebView模式下正常但在Skyline模式下如果在頁面onLoad里立刻獲取部分機(jī)型上menu返回的top不準(zhǔn)確導(dǎo)致導(dǎo)航欄高度忽高忽低。不是每次都錯而是偶發(fā)這種問題最折磨人。后來我在app啟動時(shí)把膠囊信息緩存到全局進(jìn)入頁面后直接用全局緩存不再現(xiàn)取。另外把獲取時(shí)機(jī)推遲到onReady之后數(shù)值就穩(wěn)了。這個(gè)問題的本質(zhì)是Skyline下膠囊按鈕的位置計(jì)算依賴渲染層的首幀布局頁面還沒完成布局時(shí)拿到的坐標(biāo)是有誤差的。順便說一句部分安卓機(jī)的狀態(tài)欄高度在折疊屏或異形屏上會變化建議在wx.onWindowResize里也更新一次緩存。4.2 折線圖Canvas從id到實(shí)例差一步就白屏做數(shù)據(jù)面板時(shí)我用過wx.createCanvasContext的老接口切到Skyline后發(fā)現(xiàn)canvas不繪制或繪制完一片空白。查了文檔才知道Skyline對Canvas的支持更傾向于Canvas 2D新接口給canvas標(biāo)簽加type2d然后通過SelectorQuery拿到node節(jié)點(diǎn)再取ctx。還有幾個(gè)我實(shí)測遇到的點(diǎn)如果canvas在scroll-view里獲取節(jié)點(diǎn)一定要在onReady之后并且等滾動容器完成布局繪制折線圖這類高頻更新場景基于坐標(biāo)變換的canvas在部分安卓機(jī)上會出現(xiàn)鋸齒把canvas的width和height設(shè)成CSS尺寸的2倍甚至3倍再用style縮小到100%清晰度會好很多圖片旋轉(zhuǎn)如果直接對canvas里的drawImage做旋轉(zhuǎn)建議用ctx.translate加ctx.rotate不要直接改圖片的mode或style后者在Skyline下容易出現(xiàn)旋轉(zhuǎn)后位置偏移。4.3 附件保存路徑USER_DATA_PATH不是user_data_path網(wǎng)上很多帖子寫附件保存路徑時(shí)用的是wx.env.user_data_path全小寫。實(shí)際官方環(huán)境變量是wx.env.USER_DATA_PATH全大寫。別笑我在Skyline真機(jī)調(diào)試時(shí)就是因?yàn)榫W(wǎng)上抄了一段小寫版本結(jié)果文件保存失敗、頁面卻沒有明顯報(bào)錯最后打印wx.env才發(fā)現(xiàn)問題。另外在Skyline模式下這個(gè)路徑下保存的文件如果想傳給canvas或者預(yù)覽組件不同端的臨時(shí)文件轉(zhuǎn)換規(guī)則有差異。我踩過的一個(gè)坑是文件已經(jīng)寫到本地userDataPath了但canvas的drawImage直接傳這個(gè)路徑畫不出來必須先通過FileSystemManager讀取再轉(zhuǎn)臨時(shí)路徑。穩(wěn)妥做法是用wx.getFileSystemManager().copyFile復(fù)制到臨時(shí)目錄或者直接使用臨時(shí)文件API處理不要假設(shè)本地路徑在任何場景下都能直接消費(fèi)。5. 網(wǎng)絡(luò)層與調(diào)試期一個(gè)被我誤判為服務(wù)器故障的握手報(bào)錯5.1 invalid upgrade header: null的完整排查過程某次在Skyline模式下做真機(jī)預(yù)覽控制臺出現(xiàn)handshake failed due to invalid upgrade header: nullWebSocket連接一直失敗。當(dāng)時(shí)第一反應(yīng)是服務(wù)端掛了但網(wǎng)頁端、小程序WebView模式都能正常連上同一個(gè)wss地址只有Skyline真機(jī)能穩(wěn)定復(fù)現(xiàn)。排查過程我按順序來開發(fā)者工具Skyline模擬器里一切正常排除本地代碼語法問題真機(jī)切換成WebView渲染W(wǎng)ebSocket正常初步鎖定跟Skyline有關(guān)用第三方WebSocket測試頁在同一臺手機(jī)上連同一個(gè)wss地址正??捶?wù)端Nginx日志發(fā)現(xiàn)來自小程序的握手請求經(jīng)過代理后Upgrade頭變成了空值服務(wù)端返回400確認(rèn)代理層配置里沒有顯式透傳Upgrade和Connection頭某些請求頭在特定客戶端下被合并或剝離。解決方案是在Nginx代理配置里顯式加上proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade;或者把WebSocket請求走單獨(dú)的專用通道不在普通HTTP代理后面過。5.2 它到底是不是Skyline的鍋?zhàn)詈蠼Y(jié)論不完全是。Skyline模式下網(wǎng)絡(luò)請求的發(fā)起方式更接近原生不會像WebView那樣對協(xié)議頭做各種兼容修復(fù)所以同一個(gè)服務(wù)端配置在WebView下僥幸能連在Skyline下就現(xiàn)出原形。換個(gè)說法它不是Skyline引入的bug而是Skyline把鏈路里隱藏的問題暴露出來了。這個(gè)經(jīng)驗(yàn)對我的啟發(fā)是以后排查類似問題不要一上來就怪渲染引擎。先把WebView和Skyline做對照如果Skyline有問題而WebView正常大概率是代碼里用了WebView才有的兼容行為或者服務(wù)端對協(xié)議頭處理不嚴(yán)格。逐層排查最后再動服務(wù)端配置。6. 遷移策略與兜底方案別一次性全局切6.1 哪些頁面適合先遷我踩完這些坑之后對Skyline的態(tài)度是值得用但要按頁面評估。適合先遷移的頁面有幾個(gè)特征以卡片流、長列表、橫向滑動為主滾動流暢度要求高頁面里沒有復(fù)雜彈層或者彈層組件本身支持掛載節(jié)點(diǎn)配置動畫多比如轉(zhuǎn)場、點(diǎn)贊、拖拽Skyline的worklet動畫能明顯提升體驗(yàn)。不適合一上來就切的大量使用web-view、map、video等原生組件的頁面這些組件在Skyline下要么有額外適配要求要么行為差異很大依賴第三方組件庫且組件庫沒做過Skyline適配的常見表現(xiàn)就是彈層錯位、下拉菜單不跟隨業(yè)務(wù)邏輯里大量在onLoad階段調(diào)用組件方法的頁面。6.2 Skyline降級與WebView共存如果真的全局開了Skyline后發(fā)現(xiàn)某頁面實(shí)在搞不定不用回滾全部??梢栽陧撁娴膉son里單獨(dú)寫{ renderer: webview }這樣這個(gè)頁面會繼續(xù)用WebView渲染其他頁面走Skyline。小程序框架會在后臺自動處理兩種渲染模式的共存但要注意同一次跳轉(zhuǎn)棧里盡量不要混用不同渲染模式的頁面容易出現(xiàn)過渡動畫異常。另外如果你拿不準(zhǔn)當(dāng)前環(huán)境是否支持Skyline可以在代碼里用wx.getSkylineInfoSync判斷不支持就提示用戶升級微信版本或者走一套降級UI。我最后給團(tuán)隊(duì)定的規(guī)范是新頁面默認(rèn)按Skyline設(shè)計(jì)但上線前必須過一遍真機(jī)自測清單清單里除了功能流程還包括iPhone低端機(jī)滾動、彈層定位、WebSocket連接三項(xiàng)。最后再分享一個(gè)心態(tài)上的建議切Skyline不是改個(gè)配置就完事它更像一次渲染層的架構(gòu)升級。遇到問題先記錄現(xiàn)象、縮小范圍別急著懷疑引擎。這套踩坑記錄里的很多問題后來回看都是因?yàn)槲野裇kyline當(dāng)成了WebView的加速版而不是一個(gè)新的運(yùn)行環(huán)境。如果你也準(zhǔn)備切建議從一個(gè)列表頁開始逐步摸清它的脾氣。