ตารางและรายการขนาดใหญ่มากอาจทำให้ประสิทธิภาพของเว็บไซต์ช้าลงอย่างมาก ระบบเสมือนจริงช่วยคุณได้
react-window
เป็นห้องสมุดที่ช่วยแสดงผลรายการขนาดใหญ่ได้อย่างมีประสิทธิภาพ
ต่อไปนี้คือตัวอย่างรายการที่มี 1,000 แถวที่แสดงผลด้วย 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
จะเป็นผู้รับผิดชอบในเรื่องนี้
ตัวอย่าง 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 Load ด้วยเมื่อผู้ใช้เลื่อนลง แพ็กเกจแยกต่างหากอย่าง
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 รายจาก random user 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 Load เมื่อผู้ใช้เลื่อน - ใช้พร็อพเพอร์ตี้
overscanCount
สำหรับรายการ และพร็อพเพอร์ตี้overscanColumnsCount
และoverscanRowsCount
สำหรับตารางกริดเพื่อป้องกันไม่ให้เนื้อหาว่างเปล่าปรากฏขึ้น อย่าสแกนรายการมากเกินไปเนื่องจากจะส่งผลเสียต่อประสิทธิภาพ