大型表格和清單會使網站效能明顯降低。虛擬化就能派上用場!
react-window
程式庫可讓大型清單有效率地轉譯。
以下清單範例包含 1,000 個使用 react-window
轉譯的資料列。請盡可能加快捲動速度。
這種報表有哪些優點?
有時候,您需要顯示包含許多資料列的大型資料表或清單。載入這類清單上的每個項目可能會對效能產生重大影響。
清單虛擬化 (或稱「視窗化」) 的概念僅轉譯使用者可見的內容。最先算繪的元素數量是整份清單的一小部分,當使用者繼續捲動時,可見內容的「視窗」會移動。這可以改善清單的轉譯和捲動效能。
結束「視窗」的 DOM 節點會回收,或在使用者向下捲動清單時立即替換為較新的元素。這樣就能確保所有已轉譯元素數量都符合視窗大小。
回應視窗
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
,您可以將資料欄 (而非值) 傳入各自的屬性中,藉此變更資料欄寬度和列高度。
請參閱說明文件,查看虛擬化格線範例。
捲動時延遲載入
許多網站都會藉由等待載入並顯示長清單中的項目,直到使用者向下捲動,藉此提升效能。這項技巧通常稱為「無限載入」,會在使用者捲動經過接近末端的特定門檻時,將新的 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
內。指派給載入器的道具如下:
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
屬性,以免空白內容閃爍。請勿過度掃描太多項目,以免對效能造成負面影響。