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