ตารางและรายการขนาดใหญ่พิเศษสามารถชะลอประสิทธิภาพของเว็บไซต์ได้อย่างมาก ระบบเสมือนจริงช่วยคุณได้!
react-window
เป็นไลบรารีที่ช่วยให้แสดงผลรายการขนาดใหญ่ได้อย่างมีประสิทธิภาพ
ต่อไปนี้คือตัวอย่างของรายการที่มี 1,000 แถวที่แสดงผลด้วย react-window
ลองเลื่อนให้เร็วที่สุดเท่าที่จะทำได้
มีประโยชน์อย่างไร
อาจมีบางครั้งที่คุณจำเป็นต้องแสดงตารางขนาดใหญ่หรือรายการที่มีหลายแถว การโหลดข้อมูลทุกรายการในรายการดังกล่าวอาจส่งผลต่อประสิทธิภาพอย่างมาก
ระบบรายการเสมือน หรือ "การจัดกรอบเวลา" เป็นแนวคิดในการแสดงผลเฉพาะสิ่งที่แสดงต่อผู้ใช้เท่านั้น จํานวนองค์ประกอบที่แสดงผลในตอนแรกเป็นเพียงส่วนเล็กๆ ของรายการทั้งหมดและ "หน้าต่าง" ของเนื้อหาที่มองเห็นได้จะเคลื่อนไหวเมื่อผู้ใช้เลื่อนดูต่อไป วิธีนี้ช่วยปรับปรุงทั้งการแสดงผลและ ประสิทธิภาพการเลื่อนของรายการ
![หน้าต่างเนื้อหาในรายการเสมือน](https://web.dev/static/articles/virtualize-long-lists-react-window/image/window-content-a-virtua-9c3c7f0cbf9a5.jpg?authuser=6&hl=th)
โหนด 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
ยังรองรับการสร้างรายการหรือตารางกริดแบบหลายมิติเสมือนจริงอีกด้วย ในบริบทนี้ "หน้าต่าง" ของเนื้อหาที่มองเห็นได้จะเปลี่ยนเมื่อผู้ใช้เลื่อนในแนวนอนและแนวตั้ง
![หน้าต่างที่เลื่อนได้ของเนื้อหาในตารางกริดเสมือนจริงเป็น 2 มิติ](https://web.dev/static/articles/virtualize-long-lists-react-window/image/moving-window-content-a-3393c2a96ff59.jpg?authuser=6&hl=th)
ในทำนองเดียวกัน คุณใช้ทั้งคอมโพเนนต์ FixedSizeGrid
และ VariableSizeGrid
ได้ โดยขึ้นอยู่กับว่าขนาดของรายการย่อยที่เฉพาะเจาะจงอาจแตกต่างกันไปหรือไม่
- สำหรับ
FixedSizeGrid
API นั้นใกล้เคียงกัน แต่ทั้งคอลัมน์และแถวจะต้องแสดงความสูง ความกว้าง และจำนวนรายการ - สำหรับ
VariableSizeGrid
ทั้งความกว้างของคอลัมน์และความสูงของแถวจะเปลี่ยนแปลงได้โดยส่งฟังก์ชันแทนค่าไปยังอุปกรณ์ประกอบที่เกี่ยวข้อง
ดูตัวอย่างตารางกริดเสมือนได้ในเอกสารประกอบ
การโหลดแบบ Lazy Loading เมื่อเลื่อน
เว็บไซต์จำนวนมากจะปรับปรุงประสิทธิภาพโดยการรอโหลดและแสดงผลรายการในรายการที่ยาวจนกว่าผู้ใช้จะเลื่อนหน้าเว็บลง เทคนิคนี้ หรือที่เรียกกันโดยทั่วไปว่า "การโหลดแบบไม่สิ้นสุด" จะเพิ่มโหนด DOM ใหม่ลงในรายการเมื่อผู้ใช้เลื่อนผ่านเกณฑ์บางอย่างเมื่อใกล้สิ้นสุด แม้ว่าวิธีนี้จะดีกว่าการโหลดรายการทั้งหมดในครั้งเดียว แต่ระบบยังคงเติมข้อมูล DOM ด้วยรายการหลายพันแถวถ้าผู้ใช้เลื่อนผ่านไป ซึ่งอาจทำให้ DOM มีขนาดใหญ่เกินไป ซึ่งจะเริ่มส่งผลต่อประสิทธิภาพโดยทำให้การคำนวณรูปแบบและการเปลี่ยนแปลง DOM ช้าลง
แผนภาพต่อไปนี้อาจช่วยสรุปได้
![ความแตกต่างในการเลื่อนดูรายการปกติและรายการเสมือน](https://web.dev/static/articles/virtualize-long-lists-react-window/image/difference-scrolling-bet-517c003019905.jpg?authuser=6&hl=th)
วิธีที่ดีที่สุดในการแก้ปัญหานี้คือใช้ไลบรารีอย่างเช่น react-window
ต่อไปเพื่อคง "หน้าต่าง" เล็กๆ ขององค์ประกอบในหน้าเว็บไว้ แต่ยังโหลดรายการใหม่ๆ แบบ Lazy Loading ด้วยในขณะที่ผู้ใช้เลื่อนลง แพ็กเกจ 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
เป็นแอตทริบิวต์ที่ต้องส่งผ่าน
ต่อไปนี้เป็นตัวอย่างว่าการโหลดแบบไม่สิ้นสุดสามารถทำงานกับรายการเสมือนได้อย่างไร
เมื่อเลื่อนลงมาในรายการก็อาจรู้สึกเหมือนเดิม แต่ตอนนี้ระบบจะสร้างคำขอในการเรียกผู้ใช้ 10 คนจาก API ผู้ใช้แบบสุ่มทุกครั้งที่คุณเลื่อนหน้าจอไปจนสุดรายการ ซึ่งทั้งหมดนี้ทำได้ในขณะแสดงผล "หน้าต่าง" เดียวเท่านั้นในแต่ละครั้ง
การตรวจสอบ 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 การสแกนโอเวอร์สแกนมากกว่า 1 รายการจะช่วยป้องกันพื้นที่ว่างที่เห็นได้ชัดเมื่อผู้ใช้เลื่อนหน้าจอ ทั้งนี้ขึ้นอยู่กับขนาดของรายการและขนาดของแต่ละรายการ อย่างไรก็ตาม การสแกนรายการมากเกินไปอาจส่งผลเสียต่อประสิทธิภาพ จุดมุ่งหมายทั้งหมดของการใช้รายการเสมือนคือการลดจำนวนรายการที่ผู้ใช้จะเห็นในช่วงเวลาหนึ่งๆ ดังนั้นให้พยายามรักษาจำนวนรายการที่สแกนมากเกินไปให้น้อยที่สุดเท่าที่จะเป็นไปได้
สำหรับ FixedSizeGrid
และ VariableSizeGrid
ให้ใช้พร็อพเพอร์ตี้ overscanColumnsCount
และ overscanRowsCount
เพื่อควบคุมจำนวนคอลัมน์และแถวที่จะโอเวอร์สแกนตามลำดับ
บทสรุป
หากไม่แน่ใจว่าควรเริ่มการจำลองรายการและตารางในแอปพลิเคชันของคุณที่ใด ให้ทำตามขั้นตอนต่อไปนี้
- วัดประสิทธิภาพการแสดงผลและการเลื่อน บทความนี้แสดงวิธีใช้เครื่องวัด FPS ในเครื่องมือสำหรับนักพัฒนาเว็บใน Chrome เพื่อสํารวจประสิทธิภาพการแสดงผลรายการต่างๆ ในรายการ
- ใส่
react-window
สำหรับรายการหรือตารางกริดแบบยาวที่มีผลต่อประสิทธิภาพ - หากมีฟีเจอร์บางอย่างที่
react-window
ไม่รองรับ โปรดลองใช้react-virtualized
หากคุณเพิ่มฟังก์ชันการทำงานนี้ด้วยตนเองไม่ได้ - รวมรายการเสมือนด้วย
react-window-infinite-loader
หากต้องการโหลดรายการแบบ Lazy Loading ขณะที่ผู้ใช้เลื่อนดู - ใช้พร็อพเพอร์ตี้
overscanCount
สำหรับรายการ รวมถึงพร็อพเพอร์ตี้overscanColumnsCount
และoverscanRowsCount
สำหรับตารางกริดเพื่อป้องกันการแสดงเนื้อหาที่ว่างเปล่า อย่าสแกนข้อมูลมากเกินไปเพราะจะส่งผลเสียต่อประสิทธิภาพ