จำลองรายการขนาดใหญ่ด้วยหน้าต่างแสดงปฏิกิริยา

ตารางและรายการขนาดใหญ่มากอาจทำให้ประสิทธิภาพของเว็บไซต์ช้าลงอย่างมาก ระบบเสมือนจริงช่วยคุณได้

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 ยังรองรับการจำลองรายการหรือตารางหลายมิติด้วย ในบริบทนี้ "กรอบเวลา" ของเนื้อหาที่มองเห็นได้จะเปลี่ยนแปลงเมื่อผู้ใช้เลื่อนในแนวนอนและแนวตั้ง

หน้าต่างเนื้อหาที่เลื่อนในตารางเสมือนเป็นแบบ 2 มิติ
การย้าย "กรอบ" ของเนื้อหาในตารางกริดเสมือนเป็นแบบ 2 มิติ

ในทำนองเดียวกัน คุณสามารถใช้คอมโพเนนต์ทั้ง 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 เพื่อควบคุมจำนวนคอลัมน์และแถวที่จะสแกนมาเกินขอบเขตตามลำดับ

บทสรุป

หากไม่แน่ใจว่าควรเริ่มการจำลองรายการและตารางในแอปพลิเคชันอย่างไร ให้ทำตามขั้นตอนต่อไปนี้

  1. วัดประสิทธิภาพการแสดงผลและการเลื่อน บทความนี้แสดงวิธีใช้เครื่องมือวัด FPS ในเครื่องมือสำหรับนักพัฒนาเว็บใน Chrome เพื่อดูประสิทธิภาพการแสดงผลรายการในรายการ
  2. ใส่ react-window สำหรับรายการหรือตารางกริดที่ยาวซึ่งส่งผลต่อประสิทธิภาพ
  3. หากมีฟีเจอร์บางอย่างที่ react-window ไม่รองรับ ให้ลองใช้ react-virtualized หากคุณไม่สามารถเพิ่มฟังก์ชันนี้ด้วยตนเอง
  4. ตัดรายการเสมือนด้วย react-window-infinite-loader หากต้องการโหลดรายการแบบ Lazy Load เมื่อผู้ใช้เลื่อน
  5. ใช้พร็อพเพอร์ตี้ overscanCount สำหรับรายการ และพร็อพเพอร์ตี้ overscanColumnsCount และ overscanRowsCount สำหรับตารางกริดเพื่อป้องกันไม่ให้เนื้อหาว่างเปล่าปรากฏขึ้น อย่าสแกนรายการมากเกินไปเนื่องจากจะส่งผลเสียต่อประสิทธิภาพ