H5 页面列表缓存方案
大家好,我是若川(點(diǎn)這里加我微信?ruochuan12,長期交流學(xué)習(xí))。今天給大家介紹一下關(guān)于h5頁面的列表緩存方案。感謝屏幕前的你一直關(guān)注著我。
點(diǎn)擊下方卡片關(guān)注我、加個(gè)星標(biāo),或者查看源碼等系列文章。學(xué)習(xí)源碼整體架構(gòu)系列、年度總結(jié)、JS基礎(chǔ)系列
前言
在 H5 日常開發(fā)中,會(huì)經(jīng)常遇到列表點(diǎn)擊進(jìn)入詳情頁面然后返回列表的情況,對(duì)于電商類平臺(tái)尤為常見,像我們平常用的淘寶、京東等電商平臺(tái)都是做了緩存,而且不只是列表,很多地方都用到了緩存。但剛才說的都是 App,在原生 App 中,頁面是一層層的 View,蓋在?LastPage?上,天然就能夠保存上一個(gè)頁面的狀態(tài),而 H5 不同,從詳情返回到列表后,狀態(tài)會(huì)被清除掉,重新走一遍生命周期,會(huì)重新發(fā)起請(qǐng)求,會(huì)有新的狀態(tài)寫入,對(duì)于分頁接口,列表很長,當(dāng)用戶翻了好幾頁后,點(diǎn)擊詳情看看商品詳情后再返回列表,此時(shí)頁面回到第一頁,這樣用戶體驗(yàn)很差,如果在進(jìn)入詳情的時(shí)候?qū)⒘斜頂?shù)據(jù)緩存起來,返回列表的時(shí)候用緩存數(shù)據(jù),而不是重新請(qǐng)求數(shù)據(jù),停留在離開列表頁時(shí)的瀏覽位置;或者是能夠像 App 那樣,將頁面一層層堆疊在?LastPage?上,返回的時(shí)候展示對(duì)應(yīng)的頁面,這樣用戶體驗(yàn)會(huì)好很多,本文簡單介紹一下在自己在做列表緩存的時(shí)候考慮的幾點(diǎn),后附簡單實(shí)現(xiàn)。
思考
狀態(tài)丟失的原因
通常在頁面開發(fā)中,我們是通過路由去管理不同的頁面,常用的路由庫也有很多,譬如:React-Router (https://react-guide.github.io/react-router-cn/),Dva-router (https://dvajs.com/api/#dva-router)... 當(dāng)我們切換路由時(shí),沒有被匹配到的?Component?也會(huì)被整體替換掉,原有的狀態(tài)也丟失了。因此,當(dāng)用戶從詳情頁退回到列表頁時(shí),會(huì)重新加載列表頁面組件,重新走一遍生命周期,獲取的就是第一頁的數(shù)據(jù),從而回到了列表頂部,下面是常用的路由匹配代碼段。
function?RouterConfig({?history,?app?})?{const?routerData?=?getRouterData(app);return?(<ConnectedRouter?history={history}><Routepath="/"render={(props)?=>?<Layouts?routerData={routerData}?{...props}?/>}redirectPath="/exception/403"/></ConnectedRouter>); } //?路由配置說明(你不用加載整個(gè)配置, //?只需加載一個(gè)你想要的根路由, //?也可以延遲加載這個(gè)配置)。 React.render((<Router><Route?path="/"?component={App}><Route?path="about"?component={About}/><Route?path="users"?component={Users}><Route?path="/user/:userId"?component={User}/></Route><Route?path="*"?component={NoMatch}/></Route></Router> ),?document.body)如何解決
原因找到了,那么我們?cè)趺慈ゾ彺骓撁婊蛘邤?shù)據(jù)呢?一般有兩種解決方式:1. 路由切換時(shí)自動(dòng)保存狀態(tài)。2. 手動(dòng)保存狀態(tài)。在?Vue?中,可以直接使用?keep-alive?來實(shí)現(xiàn)組件緩存,只要使用了?keep-alive?標(biāo)簽包裹的組件,在頁面切換的時(shí)候會(huì)自動(dòng)緩存?失活?的組件,使用起來非常方便,簡單例子如下。
<!--?失活的組件將會(huì)被緩存!--> <keep-alive><component?v-bind:is="currentTabComponent"></component> </keep-alive>但是,React?中并沒有?keep-alive?這種類似的標(biāo)簽或功能,官方認(rèn)為這個(gè)功能容易造成內(nèi)存泄漏,暫不考慮支持 (https://github.com/facebook/react/issues/12039)。
所以只能是在路由層做手腳,在路由切換時(shí)做對(duì)應(yīng)的緩存操作,之前有開發(fā)者提出了一種方案:通過樣式來控制組件的顯示/隱藏 (https://github.com/facebook/react/issues/12039),但是這可能會(huì)有問題,例如切換組件的時(shí)候無法使用動(dòng)畫,或者使用?Redux、Mobx?這樣的數(shù)據(jù)流管理工具,還有開發(fā)者通過?React.createPortal?API?實(shí)現(xiàn)了?React?版本的?React Keep Alive?(https://github.com/Sam618/react-keep-alive),并且使用起來也比較方便。第二種解決方案就是手動(dòng)保存狀態(tài),即在頁面卸載時(shí)手動(dòng)將頁面的狀態(tài)收集存儲(chǔ)起來,在頁面掛載的時(shí)候進(jìn)行數(shù)據(jù)恢復(fù),個(gè)人采用的就是簡單粗暴的后者,實(shí)現(xiàn)上比較簡單。緩存緩存,無外乎就是兩件事,存和取,那么在存、取的過程中需要注意哪些問題呢?
個(gè)人認(rèn)為需要注意的有以下幾點(diǎn):
存什么?何時(shí)存?存在哪?何時(shí)取?在哪取?
存什么
首先我們需要關(guān)心的是:存什么?既然要緩存,那么我們要存的是什么?是緩存整個(gè)?Component、列表數(shù)據(jù)還是滾動(dòng)容器的?scrollTop。舉個(gè)例子,微信公眾號(hào)里的文章就做了緩存,任意點(diǎn)擊一篇文章瀏覽,瀏覽到一半后關(guān)閉退出,再一次打開該文章時(shí)會(huì)停留在之前的位置,而且大家可以自行測(cè)試一下,再次打開的時(shí)候文章數(shù)據(jù)是重新獲取的,在這種場(chǎng)景下,是緩存了文章詳情滾動(dòng)容器的滾動(dòng)高度,在離開頁面的時(shí)候存起來,再次進(jìn)入的時(shí)候拿到數(shù)據(jù)后跳轉(zhuǎn)到之前的高度,除此之外,還有很多別的緩存的方式,可以緩存整個(gè)頁面,緩存?state?的數(shù)據(jù)等等,這些都可以達(dá)到我們想要的效果,具體用哪一種要看具體的業(yè)務(wù)場(chǎng)景。
何時(shí)存
其次,我們需要考慮的是什么時(shí)候存,頁面跳轉(zhuǎn)時(shí)會(huì)有多種?action?導(dǎo)航操作,比如:POP、PUSH、REPLACE?等,當(dāng)我們結(jié)合一些比較通用的路由庫時(shí),action?會(huì)區(qū)分的更加細(xì)致,對(duì)于不同的?action?在不同的業(yè)務(wù)場(chǎng)景下處理的方式也不盡相同。還是拿微信公眾號(hào)舉例,文章詳情頁面就是無腦存,無論是?PUSH、POP?都會(huì)存高度數(shù)據(jù),所以我們無論跳轉(zhuǎn)多少次頁面,再次打開總能跳轉(zhuǎn)到之前離開時(shí)的位置,對(duì)于商品列表的場(chǎng)景時(shí),就不能無腦存了,因?yàn)閺?List?->?Detail?->?List?需要緩存沒問題,但是用戶從?List?返回到其他頁面后再次進(jìn)入?List?時(shí),是進(jìn)入一個(gè)新的頁面,從邏輯上來說就不應(yīng)該在用之前緩存的數(shù)據(jù),而是重新獲取數(shù)據(jù)。正確的方式應(yīng)該是進(jìn)行?PUSH?操作的時(shí)候存,POP?的時(shí)候取。
存在哪
持久化緩存。如果是數(shù)據(jù)持久化可存到?URL?或?localStorage?中,放到?URL?上有一個(gè)很好點(diǎn)在于確定性,易于傳播。但?URL?可以先?pass?掉,因?yàn)樵趶?fù)雜列表的情況下,需要存的數(shù)據(jù)比較多,全部放到?URL?是不現(xiàn)實(shí)的,即使可以,也會(huì)讓?URL?顯得極其冗長,顯然不妥。localStorage?是一種方式,提供的?getItem、setItem?等 api 也足夠支持存取操作,最大支持 5M,容量也夠,通過序列化?Serialize?整合也可以滿足需求,另外?IndexDB?也不失為一種好的方式,WebSQL?已廢棄,就不考慮了,詳細(xì)可點(diǎn)擊張?chǎng)涡竦倪@篇文章《HTML5 indexedDB前端本地存儲(chǔ)數(shù)據(jù)庫實(shí)例教程》(https://www.zhangxinxu.com/wordpress/2017/07/html5-indexeddb-js-example/)查看對(duì)比。
內(nèi)存。對(duì)于不需要做持久化的列表或數(shù)據(jù)來說,放內(nèi)存可能是一個(gè)更好的方式,如果進(jìn)行頻繁的讀寫操作,放內(nèi)存中操作 I/O 速度快,方便。因此,可以放到 Redux?或?Rematch?等狀態(tài)管理工具中,封裝一些通用的存取方法,很方便,對(duì)于一般的單頁應(yīng)用來說,還可以放到全局的?window?中。
何時(shí)取
在進(jìn)入緩存頁面的時(shí)候取,取的時(shí)候又有幾種情況
當(dāng)導(dǎo)航操作為?POP?時(shí)取,因?yàn)槊慨?dāng)?PUSH?時(shí),都算是進(jìn)入一個(gè)新的頁面,這種情況是不應(yīng)該用緩存數(shù)據(jù)。
無論哪種導(dǎo)航操作都進(jìn)行取數(shù)據(jù),這種情況需要和何時(shí)存一起看待。
看具體的業(yè)務(wù)場(chǎng)景,來判斷取的時(shí)機(jī)。
在哪取
這個(gè)問題很簡單,存在哪就從哪里取。
CacheHoc?的方案
存什么:列表數(shù)據(jù) + 滾動(dòng)容器的滾動(dòng)高度
何時(shí)存:頁面離開且導(dǎo)航操作為?PUSH
存在哪:window
何時(shí)取:頁面初始化階段且導(dǎo)航操作為?POP?的時(shí)候
在哪取:window
CacheHoc?是一個(gè)高階組件,緩存數(shù)據(jù)統(tǒng)一存到?window?內(nèi),通過?CACHE_STORAGE?收斂,外部僅需要傳入?CACHE_NAME,scrollElRefs?即可,CACHE_NAME?相當(dāng)于緩存數(shù)據(jù)的?key,而?scrollElRefs?則是一個(gè)包含滾動(dòng)容器的數(shù)組,為啥用數(shù)組呢,是考慮到頁面多個(gè)滾動(dòng)容器的情況,在?componentWillUnmount?生命周期函數(shù)中記錄對(duì)應(yīng)滾動(dòng)容器的?scrollTop、state,在?constructor?內(nèi)初始化?state,在?componentDidMount?中更新?scrollTop。
簡單使用
import?React?from?'react' import?{?connect?}?from?'react-redux' import?cacheHoc?from?'utils/cache_hoc'@connect(mapStateToProps,?mapDispatch) @cacheHoc export?default?class?extends?React.Component?{constructor?(...props)?{super(...props)this.props.withRef(this)}//?設(shè)置?CACHE_NAMECACHE_NAME?=?`customerList${this.props.index}`;scrollDom?=?nullstate?=?{orderBy:?'2',loading:?false,num:?1,dataSource:?[],keyWord:?undefined}componentDidMount?()?{//?設(shè)置滾動(dòng)容器listthis.scrollElRefs?=?[this.scrollDom]//?請(qǐng)求數(shù)據(jù),更新?state}render?()?{const?{?history?}?=?this.propsconst?{?dataSource,?orderBy,?loading?}?=?this.statereturn?(<div?className={gcmc('wrapper')}><MeScrollclassName={gcmc('wrapper')}getMs={ref?=>?(this.scrollDom?=?ref)}loadMore={this.fetchData}refresh={this.refresh}up={{page:?{num:?1,?//?當(dāng)前頁碼,默認(rèn)0,回調(diào)之前會(huì)加1,即callback(page)會(huì)從1開始size:?15?//?每頁數(shù)據(jù)的數(shù)量//?time:?null?//?加載第一頁數(shù)據(jù)服務(wù)器返回的時(shí)間;?防止用戶翻頁時(shí),后臺(tái)新增了數(shù)據(jù)從而導(dǎo)致下一頁數(shù)據(jù)重復(fù);}}}down={{?auto:?false?}}>{loading???(<div?className={gcmc('loading-wrapper')}><Loading?/></div>)?:?(dataSource.map(item?=>?(<Cardkey={item.clienteleId}data={item}{...this.props}onClick={()?=>history.push('/detail/id')}/>)))}</MeScroll><div?className={styles['sort']}><div?className={styles['sort-wrapper']}?onClick={this._toSort}><span style={{?marginRight:?3?}}>最近下單時(shí)間</span><imgsrc={orderBy?===?'2'???SORT_UP?:?SORT_DOWN}alt='sort'style={{?width:?10,?height:?16?}}/></div></div></div>)} }效果如下:
緩存的數(shù)據(jù):
代碼
const?storeName?=?'CACHE_STORAGE' window[storeName]?=?{}export?default?Comp?=>?{return?class?CacheWrapper?extends?Comp?{constructor?(props)?{super(props)//?初始化if?(!window[storeName][this.CACHE_NAME])?{window[storeName][this.CACHE_NAME]?=?{}}const?{?history:?{?action?}?=?{}?}?=?props//?取?stateif?(action?===?'POP')?{const?{?state?=?{}?}?=?window[storeName][this.CACHE_NAME]this.state?=?{...state,}}}async?componentDidMount?()?{if?(super.componentDidMount)?{await?super.componentDidMount()}const?{?history:?{?action?}?=?{}?}?=?this.propsif?(action?!==?'POP')?returnconst?{?scrollTops?=?[]?}?=?window[storeName][this.CACHE_NAME]const?{?scrollElRefs?=?[]?}?=?this//?取?scrollTopscrollElRefs.forEach((el,?index)?=>?{if?(el?&&?el.scrollTop?!==?undefined)?{el.scrollTop?=?scrollTops[index]}})}componentWillUnmount?()?{const?{?history:?{?action?}?=?{}?}?=?this.propsif?(super.componentWillUnmount)?{super.componentWillUnmount()}if?(action?===?'PUSH')?{const?scrollTops?=?[]const?{?scrollElRefs?=?[]?}?=?thisscrollElRefs.forEach(ref?=>?{if?(ref?&&?ref.scrollTop?!==?undefined)?{scrollTops.push(ref.scrollTop)}})window[storeName][this.CACHE_NAME]?=?{state:?{...this.state},scrollTops}}if?(action?===?'POP')?{window[storeName][this.CACHE_NAME]?=?{}}}} }總結(jié)
以上的?CacheHoc?只是最簡單的一種實(shí)現(xiàn),還有很多可以改進(jìn)的地方,譬如:直接存在?window?中有點(diǎn)粗暴,多頁應(yīng)用下存到?window?會(huì)丟失數(shù)據(jù),可以考慮存到?IndexDB?或者?localStorage?中,另外這種方案若不配合上?mescroll?需要在?componentDidMount?判斷?state?內(nèi)的數(shù)據(jù),若有值就不初始化數(shù)據(jù),這算是一個(gè)?bug。
緩存方案縱有多種,但需要考慮的問題就以上幾點(diǎn)。另外在講述需要注意的五個(gè)點(diǎn)的時(shí)候,著重介紹了存什么和存在哪,其實(shí)存在哪不太重要,也不需要太關(guān)心,找個(gè)合適的地方存著就行,比較重要的是存什么、何時(shí)存,需要結(jié)合實(shí)際的應(yīng)用場(chǎng)景,來選擇合適的方式,可能不同的頁面采用的方式都不同,沒有固定的方案,重要的是分析存取的時(shí)機(jī)和位置。
最近組建了一個(gè)江西人的前端交流群,如果你也是江西人可以加我微信 ruochuan12 拉你進(jìn)群。
·················?若川出品?·················
今日話題
還有最后一天上班就放五一小長假啦(努力讓內(nèi)心喜悅不被發(fā)現(xiàn)),雖然扣除2天補(bǔ)班,?2天周末,實(shí)際天只有1天假期~哈哈,但是能連著休息5天,也還是很不錯(cuò)哦。趁著小長假可以好好放休息休息,整理一下之前沒及時(shí)整理的東西,大家五一都有什么計(jì)劃呢?歡迎在下方留言~? 歡迎分享、收藏、點(diǎn)贊、在看我的公眾號(hào)文章~
一個(gè)愿景是幫助5年內(nèi)前端人走向前列的公眾號(hào)
可加我個(gè)人微信?ruochuan12,長期交流學(xué)習(xí)
推薦閱讀
我在阿里招前端,我該怎么幫你?(現(xiàn)在還能加我進(jìn)模擬面試群)
若川知乎問答:2年前端經(jīng)驗(yàn),做的項(xiàng)目沒什么技術(shù)含量,怎么辦?
點(diǎn)擊上方卡片關(guān)注我、加個(gè)星標(biāo),或者查看源碼等系列文章。
學(xué)習(xí)源碼整體架構(gòu)系列、年度總結(jié)、JS基礎(chǔ)系列
總結(jié)
以上是生活随笔為你收集整理的H5 页面列表缓存方案的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: dreamweaver网页设计作业制作
- 下一篇: 前端学习(3293):effect ho