ตารางและรายการขนาดใหญ่มากอาจทำให้ประสิทธิภาพของเว็บไซต์ช้าลงอย่างมาก ระบบเสมือนจริงช่วยคุณได้
react-window
คือ
ไลบรารีที่ช่วยให้แสดงรายการขนาดใหญ่ได้อย่างมีประสิทธิภาพ
ต่อไปนี้คือตัวอย่างรายการที่มีการแสดงผล 1,000 แถวด้วย
react-window
ลองเลื่อนให้เร็วที่สุด
เหตุใดจึงมีประโยชน์
บางครั้งคุณอาจต้องแสดงตารางหรือรายการขนาดใหญ่ที่มีหลายแถว การโหลดทุกรายการในลิสต์ดังกล่าวอาจส่งผลต่อประสิทธิภาพอย่างมาก
การจำลองรายการหรือ "การแบ่งหน้าต่าง" คือแนวคิดของการแสดงผลเฉพาะสิ่งที่ผู้ใช้มองเห็นเท่านั้น จำนวนองค์ประกอบที่แสดงผลในตอนแรกเป็น ส่วนย่อยขนาดเล็กมากของทั้งรายการ และ "หน้าต่าง" ของเนื้อหาที่มองเห็นได้จะเลื่อน เมื่อผู้ใช้เลื่อนต่อ ซึ่งจะช่วยปรับปรุงทั้งการแสดงผลและ ประสิทธิภาพการเลื่อนของรายการ

ระบบจะรีไซเคิลโหนด 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
คุณสามารถเปลี่ยนทั้งความกว้างของคอลัมน์และความสูงของแถวได้ โดยส่งฟังก์ชันแทนค่าไปยังพร็อพที่เกี่ยวข้อง
ดูตัวอย่างตารางกริดเสมือนได้ในเอกสารประกอบ
การโหลดแบบ Lazy Loading เมื่อเลื่อน
เว็บไซต์หลายแห่งปรับปรุงประสิทธิภาพด้วยการรอโหลดและแสดงรายการในรายการยาวจนกว่าผู้ใช้จะเลื่อนลง เทคนิคนี้มักเรียกว่า "การโหลดแบบไม่สิ้นสุด" ซึ่งจะเพิ่มโหนด DOM ใหม่ลงในรายการเมื่อผู้ใช้เลื่อนผ่านเกณฑ์หนึ่งที่อยู่ใกล้จุดสิ้นสุด แม้ว่าวิธีนี้จะดีกว่าการโหลดรายการทั้งหมดในลิสต์พร้อมกัน แต่ก็ยังคงทำให้ DOM มีรายการแถวหลายพันรายการหากผู้ใช้เลื่อนผ่านรายการจำนวนมาก ซึ่งอาจทําให้ DOM มีขนาดใหญ่เกินไป ซึ่งจะเริ่มส่งผลต่อประสิทธิภาพโดยทําให้การคํานวณสไตล์และการเปลี่ยนแปลง DOM ช้าลง
แผนภาพต่อไปนี้อาจช่วยสรุปเรื่องนี้ได้

แนวทางที่ดีที่สุดในการแก้ปัญหานี้คือการใช้ไลบรารีต่อไป เช่น
react-window
เพื่อรักษา "หน้าต่าง" องค์ประกอบขนาดเล็กในหน้าเว็บ แต่ก็ให้
โหลดแบบ Lazy Entry ใหม่กว่าเมื่อผู้ใช้เลื่อนลง แพ็กเกจแยกต่างหากอย่าง 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
: การเรียกกลับที่แสดงผล Promise ซึ่งจะเปลี่ยนเป็นข้อมูลเพิ่มเติม สำหรับรายการ
Render Prop
ใช้เพื่อแสดงผลฟังก์ชันที่คอมโพเนนต์รายการใช้ในการแสดงผล
ทั้งแอตทริบิวต์ 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
สำหรับตาราง เพื่อป้องกันไม่ให้เนื้อหาว่างเปล่าปรากฏชั่วคราว อย่าสแกนรายการมากเกินไปเพราะ จะส่งผลเสียต่อประสิทธิภาพ