前言 本文先假設我們使用的是 vue + vuex + vue-router 的情況來展開討論,React 全家桶的情況應該類似。 在日常的前端研發中,我們經常會遇到如題的場景:比如從商品列表進入商品詳情,從訂單列表進入訂單詳情。先看一個 demo~ 看起來是不是還算絲滑流暢,跟客戶端效果較為接近~ ...
前言
本文先假設我們使用的是 vue + vuex + vue-router
的情況來展開討論,React
全家桶的情況應該類似。
在日常的前端研發中,我們經常會遇到如題的場景:比如從商品列表進入商品詳情,從訂單列表進入訂單詳情。先看一個 demo
~
看起來是不是還算絲滑流暢,跟客戶端效果較為接近~
正文開始
很多同學應該會說,這不是很容易麽,用 vue-router
+ transition 就好啦。
<template> <transition name="custom-classes-transition" :enter-active-class="`animated ${transitionEnter}`" :leave-active-class="`animated ${transitionLeave}`"> <router-view/> </transition> </template> <script> export default { data: () => ({ transitionEnter: '', transitionLeave: '' }), watch: { '$route' (to, from) { const toDepth = to.path.split('/').length const fromDepth = from.path.split('/').length if (toDepth < fromDepth) { this.transitionEnter = 'slideInLeft' this.transitionLeave = 'slideOutRight' } else { this.transitionEnter = 'slideInRight' this.transitionLeave = 'slideOutLeft' } } } } </script>
如上所示,slide
的動畫使用 animated.css
<style lang='scss'> $use-slideInLeft: true; $use-slideInRight: true; $use-slideOutLeft: true; $use-slideOutRight: true; import "node_modules/animate-sass/animate"; .animated { top: 0; width: 100%; height: 100%; animation-duration: calc(300ms); } .slideOutRight, .slideOutLeft { position: fixed; } </style>
然後定義好 router
的路徑規則,筆者採用 restful 的方式命名。
export default new Router({ routes: [{ // 商品列表 path: '/', name: 'auctionroom', component: () => import('app/auction-room/room/app.vue') }, { // 商品詳情 path: ':activityId', name: 'auctionroom-item', component: () => import('app/auction-room/item/app.vue') }] })
真這麼容易就能完成需求麽?
墓碑元素和路由守衛
實際情況是,我們在進入詳情頁之前,並沒有拿到詳情的數據!一般都會選擇在 vue
組件實例生命周期的 created 鉤子,獲取對應的後端數據介面。
而這個過程跟 transition
的動畫是並行的,會出現右側頁面還未拿到數據就劃入屏幕的情況。如大家所想,我們會儘量讓數據源表現的像現實世界遇到的,比如有網路延遲等等。
在這種情況發生時,其實是需要放置一個墓碑條目占位在對應位置,等到數據取到後墓碑條目會被實際內容替代。這樣設計的原因是,我們希望墓碑元素在被實際數據替代前可以有一個漂亮的過渡,而不是出現那種生硬的或者讓人迷失的效果。
先略這個醜陋的墓碑,實際情況的墓碑元素應該是有設計的,在很多新聞客戶端如今日頭條中會見到~
比起這一種方案,更常見的方式是在導航完成前獲取數據,使用 router
的 beforeRouteEnter 鉤子。這個方式固然不錯,但同樣有潛在的問題:
- 在獲取數據時,用戶會停留在當前的界面,因此建議在數據獲取期間,顯示一些進度條或者別的指示。如果數據獲取失敗,同樣有必要展示一些全局的錯誤提醒。
- 不!能!獲取組件實例
this
,因為當守衛執行前,組件實例還沒被創建
vuex + keep-alive 和返回刷新
其實在渲染詳情的時候,我們在當前列表已經有了一個商品的 Collection
,一般是個數組。那為什麼不能在進入詳情時,使用當前已有的數據做填充呢?詳情頁將這些數據立即渲染,然後再通過介面獲取其餘部分的數據,等完整數據獲取之後再回填到頁面上~
採用這個方案,我們必須引入 vuex , 才能在多個頁面組件之間傳遞數據(耗時<10毫秒),而無需等待網路響應(ajax耗時 > 50毫秒)。同時依賴後端介面對於列表和詳情的處理須保持一致,即詳情介面的欄位只可能 ≥ 列表介面的欄位。
這也是目前筆者使用的,代碼大致如下:
goDetail () { const {activityId} = this this.$store.commit('AUCTION_DETAIL', this.$props) this.$router.push({ name: 'auctionroom-item', params: { activityId }}) }
OK,進入的邏輯兼顧了流暢的動畫同時並行了數據的非同步獲取,那麼新的問題又來了!我們需要考慮另一種情況:如果用戶在列表頁下翻了很多次,那麼進入詳情頁再返回,定位得保持不變吧,怎麼解決?
分頁VS無限滾動
關於分頁,最常見的2種模式就是頁碼分頁或使用滾動條,這塊在產品設計界也經常被拿出來討論,找了2篇人人都是產品經理的文章,有興趣的同學可以延伸閱讀。
比較簡單的結論可歸納為,頁碼則適用於那些用戶在尋找特定信息的搜索結果列表頁以及那些用戶的瀏覽記錄比較重要的場合,後者適用於向Twitter等那些用戶重在消費無限的信息流而並不常搜尋特定的信息的應用,或者說前者多見於 PC
端,後者多見於 H5
。
如果是頁碼的模式,那麼返回就不再是問題了,因為翻頁信息通常會攜帶在頁面url
中,返回時我們只需要刷新當前頁面的信息就可以了。問題這次的項目是後者,產品同學無法接受進入詳情的回退讓用戶重新回頂部~
引入 keep-alive
要解決這個問題,需要使用 vue
的一個特性 keep-alive,使用原理:
- 包裹動態組件時會緩存組件實例,而不是銷毀
keep-alive
內路由切換時會調用activated
和deactivated
這兩個鉤子- 套在
router-view
外面,受到影響的範圍就是router-view
裡面的路由跳轉。
註意事項:
- 使用後會導致
created
可能不被調用,需要把一些邏輯移到activated
- 針對不需要保留狀態的情況,可以在
deactivated
中調用$destroy()
返回不刷新的問題解決了,但是產品同學又帶來了新的問題,比如用戶如果在詳情頁操作了!比如從訂單列表進入詳情後,更改了訂單狀態,那麼列表頁需要刷新這一條數據。
用 vuex
同樣可以解決這個問題,在詳情頁的 deactivated
鉤子更新列表中對應的該條數據,同樣依賴後端對於詳情和列表介面描述訂單採用同樣的數據格式,代碼大致如下:
deactivated () { this.$store.commit('AUCTION_LIST_INDEX', this.index, this.$data) }
最終效果如下:
對於訂單這種,只有用戶自己操作的數據,用這個方式就能滿足需求。但對於商品、競拍品這類,用戶訪問時間內有大量數據更新的情況,該方案其實不太完美~
我們或許需要在列表頁中緩存當前可視區域的頁碼,可以使用 vue-scroller,然後在返回時刷新當前可視區域的數據。然而這就完了嗎?你或許還是太天真!
無盡滾動的複雜度
做過 android、ios、react-native
開發的同學或許都知道大名鼎鼎的 ListView、ScrollView、RecyclerView
或許 Web 端一般沒有類似的需求,但其實你也應該知道,DOM節點越多,記憶體占用越高。我們或許需要在可視區域內,復用列表節點,回收看不見的節點,但為了保持滾動條不因為內容回收導致的塌陷而變化, 還需要對他們做合併。
我就不做過多的拆解,掘金上有一篇來自 Google 大神的譯文和一篇對應的引申:
據文中觀察,在真正產品線上使用這項技術的還比較少。可能是因為實現複雜度和收益比並不很高。但是,淘寶移動端檢索頁面實現了類似的思想。如下圖,
總結
借用:當你想提供一個高性能的有良好用戶體驗的功能時,可能技術上一個簡單的問題,就會演變成複雜問題的。這篇文章便是一個例證。隨著 “Progressive Web Apps” 逐漸成為移動設備的一等公民(會嗎?),高性能的良好體驗會變得越來越重要。開發者也必須持續的研究使用一些模式來應對性能約束。這些設計的基礎當然都是成熟的技術為根本。
我的看法:其實這種優化為什麼不是瀏覽器去做!?
最後做個小廣告,轉轉優品手機幫賣
,讓您高價賣掉自個的舊手機~ 如果對您有幫助,還請轉發到朋友圈~
如果你喜歡我們的文章,關註我們的公眾號和我們互動吧。