今天來討論比較進階的效能問題,我們俗稱的「long list」,也就是畫面上那種很長的 list 或 table,滾輪要一直滑才會到最底的那種。
程式方面要寫出來其實不難,因為很單純就是
.map() 產生 JSX 語法 HTML所以不管資料是幾十筆、幾百筆,反正程式都一樣,都是跑 .map(),只是產出來之後才會發現,雖然畫面內容都正確,卻會有失幀、lag 等效能 issue。
到了這時才會發現,寫對是一回事,寫好更是需要下功夫。
在面對 long list 的時候,其實一開始是沒什麼感覺的,畢竟剛開始沒什麼資料,畫面一下就 render 好了,就算改個 state 重新 render,也是一轉眼之間的事情。
但隨著使用者增加,資料漸增,效能方面也開始受到影響,不再是一轉眼的事情。
例如 FB、Twitter 等社群媒體會有大量的文章列表,如果有 1000 篇文章,就必須生成 1000 個 DOM 節點,每個節點底下還包含了圖片、影音等多媒體資料。
像這樣同時 render 數量龐大的元素會有幾個明顯的缺點:
React 官方建議要渲染 long list 時,可以使用 Virtualized list 技術來最佳化效能,是優化 long list 的一種技巧:
儲存所有列表元素的位置,只渲染可視區(viewport)內的列表元素,當可視區滾動時,根據滾動的 offset 大小以及所有列表元素的位置,計算在可視區應該渲染哪些元素,這種技術也叫做「Windowing」

正常的 element 排列方式是 static,每個 element 會按照順序「佔位子」,因此每個 element 是互相影響關聯的(上面的元素長愈高,下面的元素就愈被往下擠)
但為了做到 windowing 的效果,「不是所有 element 都需要 render 在畫面上」,因此需要打破這個關聯,改用 absolute
來排列,需透過每個 element 的高度,幫每個 element 直接指定一個絕對位置,這樣就可以明確知道,當我滑動到某個距離時,該 render 哪些 element
需要透過以下資料,來做出 windowing 的效果:

已知
可以算出每個 item 相對於 Long List 的絕對距離,再根據目前的滑動距離計算出,目前的 window 要顯示的 item 範圍,就可以只 render window 內的 item。
使用者滾動之後,上述流程再跑一次。
Long List
Virtualized List
Infinite scrolling
其中官方建議的兩個套件 react-window 跟 react-virtualized,作者是同一個人(Brian Vaughn)。
兩者的區別只在於, 前者是後者的優化版,更快更輕量,基本上都用優化版即可,但因為 react-window 是針對常用的 element 架構(list & grid),因此若有比較特別的架構,還是要回歸到 react-virtualized

更詳細的差異可參考 [React-window 作者本人的說法](https://github.com/bvaughn/react-window#how-is-react-window-different-from-react-virtualized
Infinite scrolling 可以參考 react-window-infinite-loader
大幅減少 render 時間,解決失幀問題,提升 UX
針對資料量龐大,但無法切不同頁面處理時,可以用來處理 HTML 格式一致、高度盡量單純的 case,尤其需要把 RWD 的狀況考慮進來。
還有許多情境可以參考 React-window 作者簡報

在公司中使用 React-window 時,其實踩了滿多坑的,剛開始套用時覺得很神奇,真的是大幅提升了 render 的速度。
但最大的坑在於,一定要確保子項目的高度在控制範圍內,比如說在 PC 版本每個子項目都寫死 60px 高度,但切換 RWD 到 mobile 版本時,可能文字太多就不小心換行,高度就被撐開了。
Virtualized List 因為是靠 absolute 定位高度,因此一定要能夠確保在任何情況下,掌控子項目高度不會暴走,否則整個 list 就會跑版很可怕。
使用 react-window 虚拟化大型列表
今晚,我想來點 Web 前端效能優化大補帖!