從列表到詳情,沒你想的那么簡單


前言

本文先假設我們使用的是 vue + vuex + vue-router 的情況來展開討論,React 全家桶的情況應該類似。

在日常的前端研發中,我們經常會遇到如題的場景:比如從商品列表進入商品詳情,從訂單列表進入訂單詳情。先看一個 demo~

Alt text

看起來是不是還算絲滑流暢,跟客戶端效果較為接近~

正文開始

很多同學應該會說,這不是很容易么,用 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 的動畫是並行的,會出現右側頁面還未拿到數據就划入屏幕的情況。如大家所想,我們會盡量讓數據源表現的像現實世界遇到的,比如有網絡延遲等等。

在這種情況發生時,其實是需要放置一個墓碑條目占位在對應位置,等到數據取到后墓碑條目會被實際內容替代。這樣設計的原因是,我們希望墓碑元素在被實際數據替代前可以有一個漂亮的過渡,而不是出現那種生硬的或者讓人迷失的效果。

Alt text

先略這個丑陋的墓碑,實際情況的墓碑元素應該是有設計的,在很多新聞客戶端如今日頭條中會見到~

比起這一種方案,更常見的方式是在導航完成前獲取數據,使用 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,使用原理:

  1. 包裹動態組件時會緩存組件實例,而不是銷毀
  2. keep-alive 內路由切換時會調用 activated 和 deactivated 這兩個鈎子
  3. 套在 router-view 外面,受到影響的范圍就是 router-view 里面的路由跳轉。

注意事項:

  • 使用后會導致 created 可能不被調用,需要把一些邏輯移到 activated
  • 針對不需要保留狀態的情況,可以在 deactivated 中調用 $destroy()

返回不刷新的問題解決了,但是產品同學又帶來了新的問題,比如用戶如果在詳情頁操作了!比如從訂單列表進入詳情后,更改了訂單狀態,那么列表頁需要刷新這一條數據。

用 vuex 同樣可以解決這個問題,在詳情頁的 deactivated 鈎子更新列表中對應的該條數據,同樣依賴后端對於詳情和列表接口描述訂單采用同樣的數據格式,代碼大致如下:

deactivated () {
  this.$store.commit('AUCTION_LIST_INDEX', this.index, this.$data)
}

 

最終效果如下:

Alt text

對於訂單這種,只有用戶自己操作的數據,用這個方式就能滿足需求。但對於商品、競拍品這類,用戶訪問時間內有大量數據更新的情況,該方案其實不太完美~

我們或許需要在列表頁中緩存當前可視區域的頁碼,可以使用 vue-scroller,然后在返回時刷新當前可視區域的數據。然而這就完了嗎?你或許還是太天真!

無盡滾動的復雜度

做過 android、ios、react-native 開發的同學或許都知道大名鼎鼎的 ListView、ScrollView、RecyclerView

或許 Web 端一般沒有類似的需求,但其實你也應該知道,DOM節點越多,內存占用越高。我們或許需要在可視區域內,復用列表節點,回收看不見的節點,但為了保持滾動條不因為內容回收導致的塌陷而變化, 還需要對他們做合並。

Alt text

我就不做過多的拆解,掘金上有一篇來自 Google 大神的譯文和一篇對應的引申:

  1. [譯] 無盡滾動的復雜度 -- 來自 Google 大神的拆解
  2. 設計無限滾動下拉加載,實踐高性能頁面真諦

Alt text

據文中觀察,在真正產品線上使用這項技術的還比較少。可能是因為實現復雜度和收益比並不很高。但是,淘寶移動端檢索頁面實現了類似的思想。如下圖,

Alt text

總結

借用:當你想提供一個高性能的有良好用戶體驗的功能時,可能技術上一個簡單的問題,就會演變成復雜問題的。這篇文章便是一個例證。隨着 “Progressive Web Apps” 逐漸成為移動設備的一等公民(會嗎?),高性能的良好體驗會變得越來越重要。開發者也必須持續的研究使用一些模式來應對性能約束。這些設計的基礎當然都是成熟的技術為根本。

我的看法:其實這種優化為什么不是瀏覽器去做!?

最后做個小廣告,轉轉優品手機幫賣,讓您高價賣掉自個的舊手機~ 如果對您有幫助,還請轉發到朋友圈~

 

 如果你喜歡我們的文章,關注我們的公眾號和我們互動吧。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM