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

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

ฮูเซน จอร์เดห์
ฮูสเซน จอร์เดห์
เจฟ พอสนิก
เจฟฟ์ พอสนิก

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

import React, { lazy } from 'react';

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

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

มีประโยชน์อย่างไร

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

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

สืบสวน

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

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

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

หากต้องการตรวจหาและจัดการกับความล้มเหลวของการโหลดแบบ Lazy Loading คุณอาจรวมคอมโพเนนต์ 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. เริ่มต้นที่ระดับเส้นทาง เส้นทางเป็นวิธีที่ง่ายที่สุดในการระบุจุดของแอปพลิเคชันที่แยกได้ ส่วนเอกสารแสดงความรู้สึกจะแสดงวิธีใช้ Suspense ร่วมกับ react-router
  2. ระบุคอมโพเนนต์ขนาดใหญ่บนหน้าเว็บบนไซต์ที่จะแสดงผลเฉพาะการโต้ตอบของผู้ใช้บางอย่าง (เช่น การคลิกปุ่ม) การแยกคอมโพเนนต์เหล่านี้จะลดเพย์โหลด JavaScript
  3. พิจารณาแยกส่วนสิ่งที่อยู่นอกหน้าจอและไม่จำเป็นต่อผู้ใช้