การแยกโค้ดด้วย React.lazy และ Suspense

คุณไม่จำเป็นต้องส่งโค้ดมากเกินกว่าที่จำเป็นให้กับผู้ใช้ ดังนั้นให้แยกแพ็กเกจกันเพื่อไม่ให้เกิดปัญหานี้ขึ้น

เมธอด React.lazy ช่วยให้แยกโค้ดแอปพลิเคชัน React ในระดับคอมโพเนนต์ได้ง่ายๆ โดยใช้การนําเข้าแบบไดนามิก

import React, { lazy } from 'react';

const AvatarComponent = lazy(() => import('./AvatarComponent'));

const DetailsComponent = () => (
  <div>
    <AvatarComponent />
  </div>
)

เหตุใดจึงมีประโยชน์

แอปพลิเคชัน React ขนาดใหญ่มักประกอบด้วยคอมโพเนนต์ วิธียูทิลิตี และไลบรารีของบุคคลที่สามหลายรายการ หากไม่มีการพยายามโหลดส่วนต่างๆ ของแอปพลิเคชันเฉพาะเมื่อจําเป็น ระบบจะส่ง JavaScript จำนวนมากชุดเดียวไปยังผู้ใช้ทันทีที่โหลดหน้าแรก ซึ่งอาจส่งผลต่อประสิทธิภาพของหน้าเว็บอย่างมาก

ฟังก์ชัน React.lazy มีวิธีแยกคอมโพเนนต์ในแอปพลิเคชันออกเป็นกลุ่ม JavaScript แยกต่างหากในตัวโดยที่คุณไม่ต้องทำอะไรมาก จากนั้นคุณก็จัดการสถานะการโหลดได้เมื่อใช้ร่วมกับคอมโพเนนต์ Suspense

สืบสวน

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

อย่างไรก็ตาม ผู้ใช้จะพบกับความล่าช้าเล็กน้อยเสมอเมื่อมีการดึงข้อมูลคอมโพเนนต์ที่มีการแยกโค้ดผ่านเครือข่าย จึงจําเป็นต้องแสดงสถานะการโหลดที่เป็นประโยชน์ การใช้ React.lazy กับคอมโพเนนต์ Suspense จะช่วยแก้ปัญหานี้ได้

import React, { lazy, Suspense } from 'react';

const AvatarComponent = lazy(() => import('./AvatarComponent'));

const renderLoader = () => <p>Loading</p>;

const DetailsComponent = () => (
  <Suspense fallback={renderLoader()}>
    <AvatarComponent />
  </Suspense>
)

Suspense ยอมรับคอมโพเนนต์ fallback ซึ่งช่วยให้คุณแสดงคอมโพเนนต์ React เป็นสถานะการโหลดได้ ตัวอย่างต่อไปนี้แสดงวิธีการทํางาน ระบบจะแสดงผลรูปโปรไฟล์ก็ต่อเมื่อมีการคลิกปุ่มเท่านั้น จากนั้นระบบจะส่งคำขอเพื่อดึงข้อมูลโค้ดที่จำเป็นสำหรับ AvatarComponent ที่ถูกระงับ ในระหว่างนี้ คอมโพเนนต์การโหลดสำรองจะแสดงขึ้นมา

ในส่วนนี้ โค้ดที่ประกอบเป็น AvatarComponent มีขนาดเล็ก ซึ่งเป็นเหตุผลที่ภาพเคลื่อนไหวที่แสดงการโหลดจะแสดงเพียงระยะเวลาสั้นๆ คอมโพเนนต์ที่มีขนาดใหญ่อาจใช้เวลาโหลดนานกว่ามาก โดยเฉพาะเมื่อมีการเชื่อมต่อเครือข่ายที่สัญญาณไม่แรงพอ

ตัวอย่างที่แสดงให้เห็นวิธีการทำงานมีดังนี้

  • หากต้องการดูตัวอย่างเว็บไซต์ ให้กดดูแอป แล้วกดเต็มหน้าจอ เต็มหน้าจอ
  • กดแป้น Control+Shift+J (หรือ Command+Option+J ใน Mac) เพื่อเปิดเครื่องมือสำหรับนักพัฒนาซอฟต์แวร์
  • คลิกแท็บเครือข่าย
  • คลิกเมนูแบบเลื่อนลงการจำกัด ซึ่งตั้งค่าเป็นไม่มีการจำกัดโดยค่าเริ่มต้น เลือก Fast 3G
  • คลิกปุ่มคลิกฉันในแอป

สัญญาณบอกสถานะการโหลดจะแสดงนานขึ้น โปรดสังเกตว่าระบบดึงข้อมูลโค้ดทั้งหมดที่ประกอบเป็น AvatarComponent เป็นกลุ่มแยกต่างหาก

แผงเครือข่ายของเครื่องมือสำหรับนักพัฒนาซอฟต์แวร์แสดงการดาวน์โหลดไฟล์ chunk.js 1 ไฟล์

การระงับคอมโพเนนต์หลายรายการ

อีกฟีเจอร์หนึ่งของ Suspense คือช่วยให้คุณระงับการโหลดคอมโพเนนต์หลายรายการได้ แม้ว่าจะโหลดแบบ Lazy Loading ทั้งหมดก็ตาม

เช่น

import React, { lazy, Suspense } from 'react';

const AvatarComponent = lazy(() => import('./AvatarComponent'));
const InfoComponent = lazy(() => import('./InfoComponent'));
const MoreInfoComponent = lazy(() => import('./MoreInfoComponent'));

const renderLoader = () => <p>Loading</p>;

const DetailsComponent = () => (
  <Suspense fallback={renderLoader()}>
    <AvatarComponent />
    <InfoComponent />
    <MoreInfoComponent />
  </Suspense>
)

วิธีนี้เป็นวิธีที่มีประโยชน์อย่างยิ่งในการเลื่อนเวลาการแสดงผลของคอมโพเนนต์หลายรายการในขณะที่แสดงสถานะการโหลดเพียงสถานะเดียว เมื่อดึงข้อมูลคอมโพเนนต์เสร็จหมดแล้ว ผู้ใช้จะเห็นองค์ประกอบทั้งหมดแสดงพร้อมกัน

คุณดูข้อมูลนี้ได้ด้วยการฝังต่อไปนี้

หากไม่มีขั้นตอนนี้ คุณอาจพบปัญหาการโหลดแบบสลับหรือ UI โหลดส่วนต่างๆ ตามลำดับโดยแต่ละส่วนมีตัวบ่งชี้การโหลดของตัวเอง ซึ่งอาจทำให้ประสบการณ์ของผู้ใช้ดูกระอักกระอ่วนมากขึ้น

จัดการกับการโหลดที่ไม่สําเร็จ

Suspense ช่วยให้คุณแสดงสถานะการโหลดชั่วคราวได้ขณะที่ระบบส่งคําขอเครือข่ายอยู่เบื้องหลัง แต่จะเกิดอะไรขึ้นหากคำขอเครือข่ายเหล่านั้นไม่สำเร็จเนื่องจากเหตุผลบางอย่าง คุณอาจออฟไลน์อยู่ หรือเว็บแอปอาจพยายามโหลดURL ที่มีเวอร์ชันแบบ Lazy Load ซึ่งล้าสมัยและไม่พร้อมใช้งานอีกต่อไปหลังจากการนําเซิร์ฟเวอร์กลับมาใช้งานอีกครั้ง

React มีรูปแบบมาตรฐานในการจัดการการโหลดที่ไม่สําเร็จประเภทเหล่านี้อย่างราบรื่นโดยใช้ขอบเขตข้อผิดพลาด ตามที่อธิบายไว้ในเอกสารประกอบ คอมโพเนนต์ React ใดก็ตามทำหน้าที่เป็นขอบเขตข้อผิดพลาดได้ หากใช้วิธีอายุการใช้งาน static getDerivedStateFromError() หรือ componentDidCatch() (หรือทั้ง 2 อย่าง)

หากต้องการตรวจหาและจัดการการโหลดแบบเลื่อนเวลาไว้ทีหลังที่ไม่สําเร็จ คุณสามารถรวมSuspense คอมโพเนนต์กับคอมโพเนนต์หลักที่ทำหน้าที่เป็นขอบเขตข้อผิดพลาด ในเมธอด render() ของขอบเขตข้อผิดพลาด คุณสามารถแสดงผลรายการย่อยตามที่เป็นอยู่หากไม่มีข้อผิดพลาด หรือแสดงผลข้อความแสดงข้อผิดพลาดที่กําหนดเองหากเกิดข้อผิดพลาด

import React, { lazy, Suspense } from 'react';

const AvatarComponent = lazy(() => import('./AvatarComponent'));
const InfoComponent = lazy(() => import('./InfoComponent'));
const MoreInfoComponent = lazy(() => import('./MoreInfoComponent'));

const renderLoader = () => <p>Loading</p>;

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = {hasError: false};
  }

  static getDerivedStateFromError(error) {
    return {hasError: true};
  }

  render() {
    if (this.state.hasError) {
      return <p>Loading failed! Please reload.</p>;
    }

    return this.props.children;
  }
}

const DetailsComponent = () => (
  <ErrorBoundary>
    <Suspense fallback={renderLoader()}>
      <AvatarComponent />
      <InfoComponent />
      <MoreInfoComponent />
    </Suspense>
  </ErrorBoundary>
)

บทสรุป

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

  1. เริ่มต้นที่ระดับเส้นทาง เส้นทางเป็นวิธีที่ง่ายที่สุดในการระบุจุดของแอปพลิเคชันที่สามารถแยก เอกสาร React จะแสดงวิธีใช้ Suspense ร่วมกับ react-router
  2. ระบุคอมโพเนนต์ขนาดใหญ่ในหน้าเว็บของเว็บไซต์ที่แสดงผลเฉพาะเมื่อผู้ใช้โต้ตอบบางอย่าง (เช่น การคลิกปุ่ม) การแยกคอมโพเนนต์เหล่านี้จะช่วยลดเพย์โหลด JavaScript
  3. พิจารณาแยกเนื้อหาอื่นๆ ที่ไม่ได้แสดงบนหน้าจอและไม่สำคัญต่อผู้ใช้