超大型資料表和清單可能會大幅降低網站效能。虛擬化功能可以提供協助!
react-window
是一種程式庫,可有效轉譯大型清單。
以下是使用 react-window
算繪的清單範例,其中包含 1000 列。請盡可能快速捲動畫面。
這種報表有哪些優點?
有時您可能需要顯示包含多個資料列的大型表格或清單。載入這類清單上的每個項目,可能會對效能造成重大影響。
清單虛擬化或「視窗化」是指只算繪使用者可見的內容。一開始算繪的元素數量是整個清單的一小部分,且當使用者繼續捲動時,可見內容的「視窗」會移動。這麼做可改善清單的算繪和捲動效能。
系統會回收離開「視窗」的 DOM 節點,或是在使用者向下捲動清單時立即替換為新的元素。這麼做可保留所有算繪元素的數量,並依視窗大小而定。
react-window
react-window
是小型第三方程式庫,可讓您在應用程式中輕鬆建立虛擬化清單。它提供許多可用於不同清單和資料表的基本 API。
使用固定大小清單的時機
如果您有大小相等的長一維清單,請使用 FixedSizeList
元件。
import React from 'react';
import { FixedSizeList } from 'react-window';
const items = [...] // some list of items
const Row = ({ index, style }) => (
<div style={style}>
{/* define the row component using items[index] */}
</div>
);
const ListComponent = () => (
<FixedSizeList
height={500}
width={500}
itemSize={120}
itemCount={items.length}
>
{Row}
</FixedSizeList>
);
export default ListComponent;
FixedSizeList
元件會接受height
、width
和itemSize
屬性,用於控制清單中項目的大小。- 會轉譯資料列的函式會以子項的形式傳遞至
FixedSizeList
。如要查看特定項目的詳細資料,請使用index
引數 (items[index]
)。 style
參數也會傳入「必須」附加至資料列元素的資料列轉譯方法。清單項目會以絕對位置顯示,並在內文中指定高度和寬度值,而style
參數負責執行這項操作。
本文稍早顯示的 Glitch 範例,就是 FixedSizeList
元件的範例。
使用可變大小名單的時機
使用 VariableSizeList
元件轉譯不同大小的項目清單。此元件的運作方式與固定大小清單相同,但會預期 itemSize
屬性的函式,而非特定值。
import React from 'react';
import { VariableSizeList } from 'react-window';
const items = [...] // some list of items
const Row = ({ index, style }) => (
<div style={style}>
{/* define the row component using items[index] */}
</div>
);
const getItemSize = index => {
// return a size for items[index]
}
const ListComponent = () => (
<VariableSizeList
height={500}
width={500}
itemCount={items.length}
itemSize={getItemSize}
>
{Row}
</VariableSizeList>
);
export default ListComponent;
以下嵌入內容為此元件的範例。
在這個範例中,傳遞至 itemSize
屬性的項目大小函式會隨機產生列高度。不過,在實際應用程式中,應有實際的邏輯定義每個項目的大小。理想情況下,這些大小應根據資料計算,或從 API 取得。
格線
react-window
也支援將多維度清單或格線虛擬化。在這個情況下,當使用者水平和垂直捲動時,可見內容的「視窗」會有所變動。
同樣地,視特定清單項目的大小是否可能有變化,您也可以使用 FixedSizeGrid
和 VariableSizeGrid
元件。
FixedSizeGrid
的 API 大致相同,但資料欄和資料列都必須呈現高度、寬度和項目數量。- 針對
VariableSizeGrid
,您可以將函式傳遞至各自的 props,而非傳遞值,藉此變更欄寬和列高。
請參閱說明文件,查看虛擬化格線的範例。
捲動時延遲載入
許多網站會等待較長的清單中載入並轉譯項目,直到使用者向下捲動為止,藉此改善效能。這項技術通常稱為「無限載入」,會在使用者捲動至接近結尾的特定門檻時,將新的 DOM 節點新增至清單中。雖然這比一次載入清單中的所有項目要好,但如果使用者捲動超過數千個項目,系統仍會在 DOM 中填入數千個資料列項目。這可能導致 DOM 大小過大,進而影響效能,因為樣式計算和 DOM 變異會變慢。
下圖可幫助您瞭解這項功能:
解決這個問題的最佳方法,就是繼續使用 react-window
等程式庫,在頁面上維持元素的小型「視窗」,但在使用者向下捲動時,也要延後載入較新的項目。獨立的套件 react-window-infinite-loader
可以使用 react-window
達成此目的。
請參考下列程式碼,其中顯示透過父項 App
元件管理的狀態範例。
import React, { Component } from 'react';
import ListComponent from './ListComponent';
class App extends Component {
constructor(props) {
super(props);
this.state = {
items: [], // instantiate initial list here
moreItemsLoading: false,
hasNextPage: true
};
this.loadMore = this.loadMore.bind(this);
}
loadMore() {
// method to fetch newer entries for the list
}
render() {
const { items, moreItemsLoading, hasNextPage } = this.state;
return (
<ListComponent
items={items}
moreItemsLoading={moreItemsLoading}
loadMore={this.loadMore}
hasNextPage={hasNextPage}
/>
);
}
}
export default App;
loadMore
方法會傳遞至包含無限載入器清單的子項 ListComponent
。這點非常重要,因為無限載入器必須在使用者捲動經過特定點後,觸發回呼才能載入更多項目。
以下是算繪清單的 ListComponent
外觀:
import React from 'react';
import { FixedSizeList } from 'react-window';
import InfiniteLoader from "react-window-infinite-loader";
const ListComponent = ({ items, moreItemsLoading, loadMore, hasNextPage }) => {
const Row = ({ index, style }) => (
{/* define the row component using items[index] */}
);
const itemCount = hasNextPage ? items.length + 1 : items.length;
return (
<InfiniteLoader
isItemLoaded={index => index < items.length}
itemCount={itemCount}
loadMoreItems={loadMore}
>
{({ onItemsRendered, ref }) => (
<FixedSizeList
height={500}
width={500}
itemCount={itemCount}
itemSize={120}
onItemsRendered={onItemsRendered}
ref={ref}
>
{Row}
</FixedSizeList>
)}
</InfiniteLoader>
)
};
export default ListComponent;
在此,FixedSizeList
元件會包裝在 InfiniteLoader
中。指派給載入器的 props 如下:
isItemLoaded
:檢查特定項目是否已載入的方法itemCount
:清單上的項目數量 (或預期項目數)loadMoreItems
:傳回承諾,可解析清單的其他資料
轉譯輔助物件用於傳回清單元件用於轉譯的函式。onItemsRendered
和 ref
屬性都需要傳入。
以下範例說明無限載入如何搭配虛擬化清單運作。
向下捲動清單的體驗可能會相同,但現在每次捲動至清單結尾時,系統都會發出要求,從隨機使用者 API 擷取 10 位使用者。這一切都是在一次只算繪製單一「視窗」的結果時完成。
查看特定項目的 index
後,項目可能會顯示不同的載入狀態,視是否曾對較新的項目發出要求,且該項目仍在載入而定。
例如:
const Row = ({ index, style }) => {
const itemLoading = index === items.length;
if (itemLoading) {
// return loading state
} else {
// return item
}
};
過掃
由於虛擬化清單中的項目只會在使用者捲動時變更,因此當新項目即將顯示時,空白空間可能會短暫閃爍。您可以嘗試快速捲動本指南先前的任何範例,看看是否有這項現象。
為改善虛擬化清單的使用者體驗,react-window
允許您使用 overscanCount
屬性對項目進行過度掃描。這樣一來,您就能定義在可見「視窗」外,要算繪多少項目。
<FixedSizeList
//...
overscanCount={4}
>
{...}
</FixedSizeList>
overscanCount
適用於 FixedSizeList
和 VariableSizeList
元件,預設值為 1。視清單大小和每個項目的大小而定,如果掃描的項目數量超過一個,就能避免使用者捲動時出現空白空間的閃爍效果。不過,如果掃描項目過多,可能會對效能造成負面影響。使用虛擬化清單的目的,是盡可能減少使用者在任何時間點可見的項目數量,因此請盡量減少過度掃描的項目數量。
針對 FixedSizeGrid
和 VariableSizeGrid
,請使用 overscanColumnsCount
和 overscanRowsCount
屬性,分別控制要進行過掃的欄和列數量。
結論
如果不確定應用程式中的清單和表格要從何處開始虛擬化,請按照下列步驟操作:
- 評估轉譯和捲動效能。這篇文章說明如何利用 Chrome 開發人員工具中的 FPS 計量功能,探索項目在清單上的算繪效率。
- 針對影響效能的長清單或格狀檢視畫面,加入
react-window
。 - 如果
react-window
不支援某些功能,且您無法自行新增此功能,請考慮使用react-virtualized
。 - 如果您需要在使用者捲動畫面時延遲載入項目,請使用
react-window-infinite-loader
納入虛擬化清單。 - 請為清單使用
overscanCount
屬性,並為格線使用overscanColumnsCount
和overscanRowsCount
屬性,以免顯示空白內容。請勿過度掃描太多項目,以免對效能造成負面影響。