項目背景
會員首頁是整個會員的承載頁,項目中集合了會員身份、金币、任務、cps、各種彈窗(至少8個),整個頁面還要支援營運日氛圍配置。
此項目有以下特性:
- 接口請求數量之多
- 頁面彈窗多,并指定優先級
- 營運氛圍支援
- 多頁面
面臨的問題
随着版本的疊代,項目體積越來越大,頁面加載速度越來越慢。
優化目标
提高加載速度,增加使用者體驗
優化前構思
通過對項目分析,大緻從以下幾個方向入手:
- 減少打包js體積
- 減少首屏資源的加載數,把其它非首屏資源渲染時機後置
- 對不敏感的資料增加一層緩存
- 減少白屏時間,提高使用者體驗
既然有了目标那就搞起~。
PS:本次主要針對項目本身做的優化,不涉及伺服器、App相關内容。
具體技術實作
通過上面的分析,下面一一對應着分析
一、減少打包js送出
由于項目建立的時候使用的老版本的create-react-app建構的,在拆包上沒有得到很好地內建,是以在用webpack建構包的時候,直接将每個頁面都輸出到了一個比較大js,在本着最小改動的情況下做了以下的事:
import React from 'react';export const asyncComponent = loadComponent => (class AsyncComponent extends React.Component {constructor(...args) {super(...args);this.state = {Component: null,
};
}componentWillMount() {if (this.hasLoadedComponent()) {return;
}
loadComponent()
.then(module => module.default ? module.default : module)
.then(Component => {this.setState({
Component
});
})
.catch(error => {/*eslint-disable*/console.error('cannot load Component in <AsyncComponent>');/*eslint-enable*/throw error;
})
}
hasLoadedComponent = () => {return this.state.Component !== null;
}render() {const {
Component
} = this.state;return (Component) ? <Component {...this.props} /> : null;
}
}
);複制代碼
通過上面asyncComponent元件動态import(/* webpackChunkName: "Index" */)引入元件,此種方式将在建構的時候會将每個路由生成一個對應的js,那我們分析一下打包之後的js包含的内容
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsISPrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdsATOfd3bkFGazxCMx8VesATMfhHLlN3XnxCMwEzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cmbw5SN2UWNlNTYlJWY1gTNxEWY0QmM0EWZiVmYwgjN4UmN28CXwMzLcJTMwIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjL0M3Lc9CX6MHc0RHaiojIsJye.png)
不難發現,在用到swiper的頁面,打包的時候每個js裡面存在,而且自己項目中的一些工具類也被分散到了5.chunk.js和6.chunk.js,這不能容忍重複引用。
經過對webpack的文檔的查找,找到CommonsChunkPlugin這個插件,可以抽取chunk的公共代碼,具體代碼如下:
new webpack.optimize.CommonsChunkPlugin({// name: 'lib',async: true,chunks: ['Index','ScoreDetail','Description','GrowthValue'],minChunks: (module) => {let context = module.context; //context是每個檔案的路徑,通過檢索目錄抽出某個目錄下面jsreturn context && (context.indexOf('memberCenter/src/lib') >= 0 || context.indexOf('node_modules/swiper/dist') >= 0);
}
}),複制代碼
再看一下我們打包輸出js,可以看到swiper,和一些工具類被打包到了一個0.chunk.js裡面,Index.chunk.js的題解也相對減少100kb,如下圖:
後面還會對這一塊做進一步優化,按樓層進行更細粒度的拆分。
二、減少首屏資源的加載數,把其它非首屏資源渲染時機後置
大家都知道其實首屏加載的内容是有限的,是以我們把首屏能展示的樓層一次加載出來,其餘不展示的樓層我們采用異步加載,當使用者滑動到指定位置的時候才會加載,這樣子達到了按需觸發異步加載,
三、對不敏感的資料增加一層緩存
大家手機上應該都安裝一些新聞之類的app,在我們首次打開的時候回發現貌似不需要請求資料就直接展示了了新聞,而且這些新聞好像之前都看過,是的你沒有看錯,這就是app本地做的緩存機制,每次新聞加載完之後都會本地緩存目前檢視的資訊,下次進來的時候直接加載緩存資料,這樣子提前了頁面渲染時機,進而達到頁面快速響應的目的,那我們也是借鑒這種思想本地也做了一層資料緩存,效果還不錯。
現在react/vue都是基于資料驅動UI的模型,天然具備了對設定的資料做差分對比,是以這一塊我們隻要保證本地緩存能被更新就好。具體實作是通過md5對請求url以及參數加密之後作為緩存的key,保證key的唯一性以及減少因為參數變化導緻取緩存錯誤。
取緩存:
let cacheKey = '';let cacheData = {};//緩存接口資料try {const encryptStr = `${this._getBaseUrl()}${functionId}${functionId.indexOf('?') === -1 ? '?' : '&'}reqData=${paramsStr}`;if (cacheEnable) {//緩存key,使用原始參數生成cacheKey = md5((this.getCookie('user_id') || '') + encryptStr);
cacheData = LocalStorageUtils.getValue(cacheKey);if (cacheData) {
cacheData = JSON.parse(cacheData);
successHandle && successHandle(cacheData.data);
}
}
} catch (e) {
}複制代碼
儲存緩存:
if (cacheEnable && JSON.stringify(cacheData) !== JSON.stringify(result)) {
LocalStorageUtils.setValue(cacheKey, JSON.stringify(result))
}複制代碼
PS:此處會看到會看到從cookie中取user_id這個屬性,主要是為了防止同一個裝置切換賬号展示資料串了,這是我們不想看到的。
四、減少白屏時間,提高使用者體驗
白屏原因:
1. 不可避免的
- 浏覽其下載下傳html的過程
2. 可優化的
- html中引用了過大過多的js,或者做了比較耗時邏輯
- react/vue渲染頁面的過程
分析+處理方案:
現在react/vue渲染都是基于js動态渲染,它會将渲染好的UI插到html指定的元素中,此時就會出現如果渲染js的體積過大會導緻加載時間較長,并且如果html中引用了一些比較大的也會導緻html加載比較慢,就會到時白屏時間加長。前面已經縮小了頁面js的體積,那邊怎們就從怎麼減少html中js的加載數,或者看看有沒有一些js可以延後加載的。我們引入的樂高、cps的js就是這樣子,不是每次進入都需要立刻加載,是以我們将其加載方式改成動态插入script的方式,具體代碼如下:
/**
* html 動态注入script标簽
* @param id * @param url */loadJS(id, url, callback) {const script = document.createElement('script');
script.id = id;
script.type = 'text/javascript'; //(下面方法線上驗證中,目前沒有出現相容問題)if (script.readyState) {
script.onreadystatechange = () => {if (script.readyState == "loaded" || script.readyState == "complete") {
script.onreadystatechange = null;
callback && callback();
}
}
} else {
script.onload = () => {
callback && callback();
}
}
script.src = url;document.body.appendChild(script);
},複制代碼
那麼我們減少了html中js的加載數量,那白屏怎麼處理呢,我們借鑒了其它廠的方式,采用骨架屏占位的方式,在html加載完之後在最上層加一個base64骨架圖檔占位,給使用者一種已經打開頁面的視覺感,增加使用者體驗。
總結:
貌似到這也差不多了,還有一些頁面中具體的邏輯細節優化,此處就不再贅。本次主要針對項目中的一些存在的問題做的優化,比較有針對性,有問題或者好的建議可與我聯系,好了來看一下優化前後對比,無圖無真相。
1、先看一下性能對比圖如下:
處理前:
NetWork:Online
NetWork:Fast 3G
處理後:
2、視訊效果對比
-
- 會員首頁-2次通路對比(左邊是優化後,右側是優化前)
- 會員首頁-首次通路對比(左邊是優化後,右側是優化前)
-
- 會員首頁-2次通路對比(左邊是優化後,右側是優化前)
- 會員首頁-首次通路對比(左邊是優化後,右側是優化前)