ตารางและรายการขนาดใหญ่มากอาจทำให้ประสิทธิภาพของเว็บไซต์ช้าลงอย่างมาก ระบบเสมือนจริงช่วยได้
react-window เป็นไลบรารีที่ช่วยให้แสดงรายการขนาดใหญ่ได้อย่างมีประสิทธิภาพ
เหตุใดจึงมีประโยชน์
บางครั้งคุณอาจต้องแสดงตารางหรือรายการขนาดใหญ่ที่มีหลายแถว การโหลดทุกรายการในรายการดังกล่าวอาจส่งผลต่อประสิทธิภาพอย่างมาก
การจำลองเสมือนของรายการ หรือ "การแบ่งหน้าต่าง" เป็นแนวคิดของการแสดงผลเฉพาะสิ่งที่ ผู้ใช้มองเห็น จำนวนองค์ประกอบที่แสดงผลในตอนแรกเป็นส่วนย่อยขนาดเล็กมากของรายการทั้งหมด และ "หน้าต่าง" ของเนื้อหาที่มองเห็นได้จะ เลื่อน เมื่อผู้ใช้เลื่อนต่อ ซึ่งจะช่วยปรับปรุงทั้งประสิทธิภาพการแสดงผลและการเลื่อนของรายการ
ระบบจะรีไซเคิลโหนด DOM ที่ออกจาก "หน้าต่าง" หรือแทนที่ด้วยองค์ประกอบใหม่กว่าทันทีเมื่อผู้ใช้เลื่อนลงในรายการ ซึ่งจะทำให้จำนวนองค์ประกอบที่แสดงผลทั้งหมดเป็นไปตามขนาดของหน้าต่าง
react-window
react-window เป็นไลบรารีขนาดเล็กของบุคคลที่สามที่ช่วยให้สร้างรายการที่จำลองเสมือนในแอปพลิเคชันได้ง่ายขึ้น โดยมี API พื้นฐานจำนวนหนึ่งที่ใช้กับรายการและตารางประเภทต่างๆ ได้
กรณีที่ควรใช้รายการขนาดคงที่
ใช้คอมโพเนนต์ FixedSizeList หากคุณมีรายการแบบ 1 มิติยาวๆ ที่มีรายการขนาดเท่ากัน
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จะเป็นผู้รับผิดชอบในส่วนนี้
กรณีที่ควรใช้รายการขนาดตัวแปร
ใช้คอมโพเนนต์ 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 ได้ทั้งคู่ ทั้งนี้ขึ้นอยู่กับว่าขนาดของรายการที่เฉพาะเจาะจงอาจแตกต่างกันหรือไม่
- สำหรับ
FixedSizeGridAPI จะคล้ายกัน แต่ความสูง ความกว้าง และจำนวนรายการต้องแสดงทั้งคอลัมน์และแถว - สำหรับ
VariableSizeGridคุณสามารถเปลี่ยนทั้งความกว้างของคอลัมน์และความสูงของแถวได้โดยส่งฟังก์ชันแทนค่าไปยังพร็อพที่เกี่ยวข้อง
ดูตัวอย่างตารางกริดที่จำลองเสมือนได้ใน เอกสารประกอบเพื่อดู
การโหลดแบบ Lazy Loading เมื่อเลื่อน
เว็บไซต์จำนวนมากปรับปรุงประสิทธิภาพโดยรอโหลดและแสดงรายการในรายการยาวๆ จนกว่าผู้ใช้จะเลื่อนลง เทคนิคนี้ซึ่งมักเรียกว่า "การโหลดแบบไม่สิ้นสุด" จะเพิ่มโหนด DOM ใหม่ลงในรายการเมื่อผู้ใช้เลื่อนผ่านเกณฑ์ที่กำหนดซึ่งอยู่ใกล้กับจุดสิ้นสุด แม้ว่าวิธีนี้จะดีกว่าการโหลดรายการทั้งหมดในรายการพร้อมกัน แต่ก็ยังคงเป็นการเพิ่มรายการแถวหลายพันรายการลงใน DOM หากผู้ใช้เลื่อนผ่านรายการจำนวนมาก ซึ่งอาจทำให้ DOM มีขนาดใหญ่เกินไป และเริ่มส่งผลต่อประสิทธิภาพโดยทำให้การคำนวณสไตล์และการเปลี่ยนแปลง DOM ช้าลง
แผนภาพต่อไปนี้อาจช่วยสรุปได้
แนวทางที่ดีที่สุดในการแก้ปัญหานี้คือการใช้ไลบรารีอย่าง react-window ต่อไปเพื่อรักษา "หน้าต่าง" องค์ประกอบขนาดเล็กในหน้าเว็บ แต่ยังโหลดรายการใหม่เมื่อจำเป็นเมื่อผู้ใช้เลื่อนลงด้วย A separate package,
react-window-infinite-loader, makes this possible with 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: Callback ที่แสดงผล Promise ที่แสดงข้อมูลเพิ่มเติมสำหรับรายการ
ระบบจะใช้
พร็อพการแสดงผล
เพื่อแสดงผลฟังก์ชันที่คอมโพเนนต์รายการใช้ในการแสดงผล
ทั้งแอตทริบิวต์ 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หากคุณต้องการโหลดรายการเมื่อจำเป็นเมื่อผู้ใช้เลื่อน - ใช้พร็อพเพอร์ตี้
overscanCountสำหรับรายการ และพร็อพเพอร์ตี้overscanColumnsCountและoverscanRowsCountสำหรับตารางกริดเพื่อป้องกันการกะพริบของเนื้อหาว่าง อย่าสแกนรายการมากเกินไปเนื่องจากจะส่งผลเสียต่อประสิทธิภาพ