運(yùn)行原理
邏輯層和視圖層分離,且非H5端通信有折損
uni-app 在非H5端運(yùn)行時(shí),從架構(gòu)上分為邏輯層和視圖層兩個(gè)部分。邏輯層負(fù)責(zé)執(zhí)行業(yè)務(wù)邏輯,也就是運(yùn)行js代碼,視圖層負(fù)責(zé)頁面渲染。
雖然開發(fā)者在一個(gè)vue頁面里寫js和css,但其實(shí),編譯時(shí)就已經(jīng)將它們拆分了。
邏輯層詳解
邏輯層是運(yùn)行在一個(gè)獨(dú)立的jscore里的,它不依賴于本機(jī)的webview,所以一方面它沒有瀏覽器兼容問題,可以在Android4.4上跑es6代碼,另一方面,它無法運(yùn)行window、document、navigator、localstorage等瀏覽器專用的js API。
jscore就是一個(gè)標(biāo)準(zhǔn)js引擎,標(biāo)準(zhǔn)js是可以正常運(yùn)行的,比如if、for、各種字符串、日期處理等。js和瀏覽器的區(qū)別要注意區(qū)分開來。
- 所謂瀏覽器的js引擎,就是jscore或v8的基礎(chǔ)上新增了一批瀏覽器專用API,比如dom;
- node.js引擎,則是v8基礎(chǔ)上補(bǔ)充一些電腦專用API,比如本地io;
- 那么uni-app的App端和小程序端的js引擎,其實(shí)是在jscore上補(bǔ)充了一批手機(jī)端常用的JS API,比如掃碼。
視圖層詳解
h5和小程序平臺,以及app-vue,視圖層是webview。而app-nvue的視圖層是基于weex改造的原生視圖。
在iOS上,所有人都只能使用iOS提供的webview。它有一定的瀏覽器兼容問題,iOS版本不同,它的表現(xiàn)有細(xì)微差異。
Android上小程序大多自帶了一個(gè)幾十M的chromium webview,而App端沒辦法帶這么大體積的三方包,所以App端使用了Android system webview,而系統(tǒng)webview跟隨手機(jī)不同而有差異。
所以uni-app的js基本沒有不同手機(jī)的兼容問題(因?yàn)閖s引擎自帶了),而視圖層的css,在app-vue上會有手機(jī)瀏覽器的css兼容問題。所以在app-vue的場景中盡量不要使用太新的css語法,除非你不打算支持低端機(jī)。
邏輯層和視圖層分離的利與弊
邏輯層和視圖層分離,好處是js運(yùn)算不卡渲染,最簡單直接的感受就是:窗體動畫穩(wěn)。
如果開發(fā)者使用過App,應(yīng)該有概念,webview新窗體一邊做進(jìn)入動畫,一邊自身渲染,很容易卡動畫。而uni-app則無需寫預(yù)載代碼,新窗體渲染快且動畫穩(wěn)定。
但是兩層分離也帶來一個(gè)壞處,這兩層互相通信,其實(shí)是有損耗的。
iOS還好,但Android低端機(jī)上,每次通信都要耗時(shí)幾十毫秒。平時(shí)看不出來影響,但有幾個(gè)場景表現(xiàn)明顯。
- 連續(xù)高幀率繪制canvas動畫,會發(fā)現(xiàn)還不如webview內(nèi)部繪制流程
- 視圖層滾動、跟手操作,不停反饋給邏輯層,js再處理邏輯并通知視圖層做對應(yīng)更新。此時(shí)會發(fā)現(xiàn)交互不跟手或卡
不管app-vue/小程序,還是app-nvue,都有相同的問題。
解決這類問題,在webview渲染和原生渲染引用了不同的做法。
在app-vue和微信小程序上,提供了一種運(yùn)行于視圖層的專屬js,微信叫做wxs,uni-app也沿用了這個(gè)名稱。
微信里對wxs限制較多,只能實(shí)現(xiàn)有限的功能,app端(尤其是v3編譯器下)則沒有限制。
wxs中可以監(jiān)聽手勢,以uni ui的swiperAction組件為例,手指拖動,側(cè)邊的列表菜單項(xiàng)要跟手滑出,此時(shí)就需要使用wxs才能實(shí)現(xiàn)流暢效果。
至于canvas動畫,微信的canvas是原生的,無法運(yùn)用wxs操作,且一樣有通信折損,所以繪制動畫的性能不佳。而uni-app的app-vue的canvas是webview的,并且支持wxs操作,開發(fā)者可以在普通js中傳遞數(shù)據(jù)和指令給wxs,在wxs里繪制動畫,將不再有通信折損,實(shí)現(xiàn)更流暢的效果(app端需v3編譯器)
在app-nvue里,折損一樣存在。包括react native也有這個(gè)問題。weex發(fā)明了一套bindingx機(jī)制,可以在js里傳一個(gè)表達(dá)式給原生,由原生解析后根據(jù)指令操作視圖層,這個(gè)技術(shù)在uni-app里也可以使用。
bindingx作為一種表達(dá)式,它的功能不及js強(qiáng)大,但基本的手勢監(jiān)聽、動畫還是可以實(shí)現(xiàn)的,比如uni ui的swiperAction組件在app-nvue下運(yùn)行時(shí)會自動啟用bindingx,以實(shí)現(xiàn)流暢跟手。
app-vue和小程序的數(shù)據(jù)更新,分頁面級和組件級
對于復(fù)雜頁面,更新某個(gè)區(qū)域的數(shù)據(jù)時(shí),需要把這個(gè)區(qū)域做成組件,這樣更新數(shù)據(jù)時(shí)就只更新這個(gè)組件,否則會整個(gè)頁面的數(shù)據(jù)更新,造成點(diǎn)擊延遲卡頓。 這就是自定義組件編譯模式的特點(diǎn)。
比如微博長列表頁面,點(diǎn)擊一個(gè)點(diǎn)贊圖標(biāo),贊數(shù)要立即+1,此時(shí)這個(gè)點(diǎn)贊按鈕一定要做成組件。否則這個(gè)+1會引發(fā)頁面級所有數(shù)據(jù)的更新。
app-nvue和h5不存在此問題。造成差異的原因是小程序目前只提供了組件差量更新的機(jī)制,不能自動計(jì)算所有頁面差量。
優(yōu)化建議
使用自定義組件模式
使用自定義組件模式,在manifest中配置自定義組件模式(HBuilderX1.9起新建項(xiàng)目默認(rèn)即為自定義組件模式)。
在復(fù)雜頁面中,頁面中嵌套大量組件,如果是非自定義組件模式,更新一個(gè)組件會導(dǎo)致整個(gè)頁面數(shù)據(jù)更新。而自定義組件模式則可以單獨(dú)更新一個(gè)組件的數(shù)據(jù)。
在App端,除了上述好處,自定義組件模式還新增了一個(gè)獨(dú)立的js引擎,加快啟動速度、減少js阻塞。
之前的非自定義組件模式已經(jīng)不再推薦,如果你的應(yīng)用還是非自定義組模式,請盡快升級。
避免使用大圖
頁面中若大量使用大圖資源,會造成頁面切換的卡頓,導(dǎo)致系統(tǒng)內(nèi)存升高,甚至白屏崩潰。
尤其是不要把多張大圖縮小后顯示在一個(gè)屏幕內(nèi),比如上傳圖片前選了數(shù)張幾M體積的照片,然后縮小在一個(gè)屏幕中展示多張幾M的大圖,非常容易白屏崩潰。
優(yōu)化數(shù)據(jù)更新
在 uni-app 中,定義在 data 里面的數(shù)據(jù)每次變化時(shí)都會通知視圖層重新渲染頁面。 所以如果不是視圖所需要的變量,可以不定義在 data 中,可在外部定義變量或直接掛載在vue實(shí)例上,以避免造成資源浪費(fèi)。
長列表
- 長列表中如果每個(gè)item有一個(gè)點(diǎn)贊按鈕,點(diǎn)擊后點(diǎn)贊數(shù)字+1,此時(shí)點(diǎn)贊組件必須是一個(gè)單獨(dú)引用的組件,才能做到差量數(shù)據(jù)更新。否則會造成整個(gè)列表數(shù)據(jù)重載。(要求自定義組件模式)
- 長列表中每個(gè)item并不一定需要做成組件,取決于你的業(yè)務(wù)中是否需要差量更新某一行item的數(shù)據(jù),如沒有此類需求則不應(yīng)該引入大量組件。(點(diǎn)擊item后背景變色,屬于css調(diào)整,沒有更新data數(shù)據(jù)和渲染,不涉及這個(gè)問題)
- app端nvue的長列表應(yīng)該使用list組件,有自動的渲染資源回收機(jī)制。vue頁面使用頁面滾動的性能,好于使用scroll-view的區(qū)域滾動。uni ui封裝了uList組件,強(qiáng)烈推薦開發(fā)者使用,避免自己寫的不好產(chǎn)生性能問題。
- 如需要左右滑動的長列表,請?jiān)贖BuilderX新建uni-app項(xiàng)目選新聞模板,那是一個(gè)標(biāo)桿實(shí)現(xiàn)。自己用swiper和scroll-view做很容易引發(fā)性能問題。
減少一次性渲染的節(jié)點(diǎn)數(shù)量
頁面初始化時(shí),邏輯層如果一次性向視圖層傳遞很大的數(shù)據(jù),使視圖層一次性渲染大量節(jié)點(diǎn),可能造成通訊變慢、頁面切換卡頓,所以建議以局部更新頁面的方式渲染頁面。如:服務(wù)端返回100條數(shù)據(jù),可進(jìn)行分批加載,一次加載50條,500ms 后進(jìn)行下一次加載。
減少節(jié)點(diǎn)嵌套層級
深層嵌套的節(jié)點(diǎn)在頁面初始化構(gòu)建時(shí)往往需要更多的內(nèi)存占用,并且在遍歷節(jié)點(diǎn)時(shí)也會更慢些,所以建議減少深層的節(jié)點(diǎn)嵌套。
避免視圖層和邏輯層頻繁進(jìn)行通訊
- 減少 scroll-view 組件的 scroll 事件監(jiān)聽,當(dāng)監(jiān)聽 scroll-view 的滾動事件時(shí),視圖層會頻繁的向邏輯層發(fā)送數(shù)據(jù);
- 監(jiān)聽 scroll-view 組件的滾動事件時(shí),不要實(shí)時(shí)的改變 scroll-top/scroll-left 屬性,因?yàn)楸O(jiān)聽滾動時(shí),視圖層向邏輯層通訊,改變 scroll-top/scroll-left 時(shí),邏輯層又向視圖層通訊,這樣就可能造成通訊卡頓。
- 注意 onPageScroll 的使用,onPageScroll 進(jìn)行監(jiān)聽時(shí),視圖層會頻繁的向邏輯層發(fā)送數(shù)據(jù);
- 多使用css動畫,而不是通過js的定時(shí)器操作界面做動畫
- 如果是canvas里做跟手操作,建議使用web-view組件。web-view里的頁面沒有邏輯層和視圖層分離的概念,自然也不會有通信折損。
優(yōu)化頁面切換動畫
- 頁面初始化時(shí)若存在大量圖片或原生組件渲染和大量數(shù)據(jù)通訊,會發(fā)生新頁面渲染和窗體進(jìn)入動畫搶資源,造成頁面切換卡頓、掉幀。建議延時(shí)100ms~300ms渲染圖片或復(fù)雜原生組件,分批進(jìn)行數(shù)據(jù)通訊,以減少一次性渲染的節(jié)點(diǎn)數(shù)量。
- App端動畫效果可以自定義。popin/popout的雙窗體聯(lián)動擠壓動畫效果對資源的消耗更大,如果動畫期間頁面里在執(zhí)行耗時(shí)的js,可能會造成動畫掉幀。此時(shí)可以使用消耗資源更小的動畫效果,比如slide-in-right/slide-out-right。
優(yōu)化背景色閃白
- 如果頁面背景是深色,在vue頁面中可能會發(fā)生新窗體剛開始動畫時(shí)是灰白色背景,動畫結(jié)束時(shí)才變?yōu)樯钌尘?,造成閃屏。這是因?yàn)閣ebview的背景生效太慢的問題。此時(shí)需將樣式寫在 App.vue 里,可以加速頁面樣式渲染速度。App.vue 里面的樣式是全局樣式,每次新開頁面會優(yōu)先加載 App.vue 里面的樣式,然后加載普通 vue 頁面的樣式。
- 還可以在pages.json的globalStyle->style->app-plus->background下配置全局背景色"style": {
"app-plus": {
"background":"#000000"
}
}
- 另外nvue頁面不存在此問題,也可以更改為nvue頁面。
使用nvue代替vue
在 App 端 uni-app 的 nvue 頁面可是基于 weex 定制的原生渲染引擎,實(shí)現(xiàn)了頁面原生渲染能力、提高了頁面流暢性。若對頁面性能要求較高可以使用此方式開發(fā),詳見:nvue。
優(yōu)化啟動速度
- 工程代碼越多,包括背景圖和本地字體文件越大,對App的啟動速度有影響,應(yīng)注意控制體積。組件引用的前景圖不影響性能。
- App端的 splash 關(guān)閉有白屏檢測機(jī)制,如果首頁一直白屏或首頁本身就是一個(gè)空的中轉(zhuǎn)頁面,可能會造成 splash 10秒才關(guān)閉,可參考此文解決https://ask.dcloud.net.cn/article/35565
- App端使用自定義組件模式時(shí)啟動速度更快,首頁為nvue頁面時(shí)啟動速度更快
- App設(shè)置為純nvue項(xiàng)目(manifest里設(shè)置app-plus下的renderer:"native"),這種項(xiàng)目的啟動速度更快,2秒即可完成啟動。因?yàn)樗麄€(gè)應(yīng)用都使用原生渲染,不加載基于webview的那套框架。
優(yōu)化包體積
- uni-app發(fā)行到小程序時(shí),自帶引擎只有幾十K,主要是一個(gè)定制過的vue.js核心庫。如果使用了es6轉(zhuǎn)es5、css對齊的功能,可能會增大代碼體積,可以配置這些編譯功能是否開啟。
- uni-app的H5端,自帶了vue.js、vue-router及部分es6 polyfill庫,這部分的體積gzip后只有92k,和web開發(fā)使用vue基本一致。而內(nèi)置組件ui庫(如picker、switch等)、小程序的對齊js api等,相當(dāng)于一個(gè)完善的大型ui庫。但大多數(shù)應(yīng)用不會用到所有內(nèi)置組件和API。由此uni-app提供了搖樹優(yōu)化機(jī)制,未搖樹優(yōu)化前的uni-app整體包體積約500k,服務(wù)器部署gzip后162k。開啟搖樹優(yōu)化需在manifest配置,詳情。
- uni-app的App端,因?yàn)樽詭Я艘粋€(gè)獨(dú)立v8引擎和小程序框架,所以比HTML5Plus或mui等普通hybrid的App引擎體積要大。Android基礎(chǔ)引擎約15M。App還提供了擴(kuò)展模塊,比如地圖、藍(lán)牙等,打包時(shí)如不需要這些模塊,可以裁剪掉,以縮小發(fā)行包體積。在 manifest.json-App模塊權(quán)限 里可以選擇。
- App端支持如果選擇純nvue項(xiàng)目(manifest里設(shè)置app-plus下的renderer:"native"),包體積可以進(jìn)一步減少2M左右。
- uni-app的App端默認(rèn)包含arm32和x86兩個(gè)cpu的支持so庫。這會增大包體積。如果你在意體積控制,可以在manifest里去掉x86 cpu的支持(manifest可視化界面-App其他設(shè)置里選擇cpu),這可以減少包體積到9M。但代價(jià)是不支持intel的cpu了。一般手機(jī)都是arm的,僅個(gè)別少見的Android pad使用x86 cpu。另外as的模擬器里如果選擇x86時(shí)也無法運(yùn)行這種apk。
更多建議: