เรียนรู้วิธีใช้รูปแบบ React Context Selector เพื่อปรับปรุง re-render และเพิ่มประสิทธิภาพในแอปพลิเคชัน React ของคุณ ตัวอย่างการใช้งานจริงและแนวทางปฏิบัติที่ดีที่สุดระดับโลกรวมอยู่ด้วย
รูปแบบ React Context Selector: การปรับปรุง Re-render เพื่อประสิทธิภาพ
React Context API เป็นวิธีที่มีประสิทธิภาพในการจัดการสถานะส่วนกลางในแอปพลิเคชันของคุณ อย่างไรก็ตาม ความท้าทายทั่วไปเกิดขึ้นเมื่อใช้ Context: การ re-render ที่ไม่จำเป็น เมื่อค่า Context เปลี่ยนแปลง คอมโพเนนต์ทั้งหมดที่ใช้ Context นั้นจะ re-render แม้ว่าคอมโพเนนต์เหล่านั้นจะขึ้นอยู่กับข้อมูล Context เพียงเล็กน้อยก็ตาม สิ่งนี้สามารถนำไปสู่ปัญหาคอขวดด้านประสิทธิภาพ โดยเฉพาะอย่างยิ่งในแอปพลิเคชันขนาดใหญ่และซับซ้อน รูปแบบ Context Selector นำเสนอโซลูชันโดยอนุญาตให้คอมโพเนนต์สมัครรับเฉพาะส่วนที่ต้องการของ Context เท่านั้น ซึ่งช่วยลดการ re-render ที่ไม่จำเป็นได้อย่างมาก
ทำความเข้าใจปัญหา: การ Re-render ที่ไม่จำเป็น
มาอธิบายเรื่องนี้ด้วยตัวอย่าง ลองนึกภาพแอปพลิเคชันอีคอมเมิร์ซที่จัดเก็บข้อมูลผู้ใช้ (ชื่อ, อีเมล, ประเทศ, การตั้งค่าภาษา, รายการในรถเข็น) ใน Context provider หากผู้ใช้อัปเดตการตั้งค่าภาษา คอมโพเนนต์ทั้งหมดที่ใช้ Context รวมถึงคอมโพเนนต์ที่แสดงเฉพาะชื่อผู้ใช้ จะ re-render สิ่งนี้ไม่มีประสิทธิภาพและอาจส่งผลกระทบต่อประสบการณ์ของผู้ใช้ พิจารณาผู้ใช้ในสถานที่ทางภูมิศาสตร์ที่แตกต่างกัน หากผู้ใช้ชาวอเมริกันอัปเดตโปรไฟล์ของตน คอมโพเนนต์ที่แสดงรายละเอียดของผู้ใช้ชาวยุโรป *ไม่ควร* re-render
เหตุใดการ Re-render จึงมีความสำคัญ
- ผลกระทบต่อประสิทธิภาพ: การ re-render ที่ไม่จำเป็นใช้รอบ CPU ที่มีค่า นำไปสู่การเรนเดอร์ที่ช้าลงและอินเทอร์เฟซผู้ใช้ที่ตอบสนองน้อยลง สิ่งนี้เห็นได้ชัดเจนโดยเฉพาะอย่างยิ่งบนอุปกรณ์ที่มีกำลังไฟต่ำกว่าและในแอปพลิเคชันที่มีแผนผังส่วนประกอบที่ซับซ้อน
- ทรัพยากรที่สูญเปล่า: การ re-render คอมโพเนนต์ที่ไม่ได้เปลี่ยนแปลงจะสูญเสียทรัพยากรเช่นหน่วยความจำและแบนด์วิดท์เครือข่าย โดยเฉพาะอย่างยิ่งเมื่อดึงข้อมูลหรือทำการคำนวณที่มีราคาแพง
- ประสบการณ์ผู้ใช้: UI ที่ช้าและไม่ตอบสนองสามารถทำให้ผู้ใช้หงุดหงิดและนำไปสู่ประสบการณ์ผู้ใช้ที่ไม่ดี
ขอแนะนำรูปแบบ Context Selector
รูปแบบ Context Selector แก้ปัญหาการ re-render ที่ไม่จำเป็นโดยอนุญาตให้คอมโพเนนต์สมัครรับเฉพาะส่วนที่ต้องการของ Context เท่านั้น ทำได้โดยใช้ฟังก์ชัน selector ที่ดึงข้อมูลที่จำเป็นจากค่า Context เมื่อค่า Context เปลี่ยนแปลง React จะเปรียบเทียบผลลัพธ์ของฟังก์ชัน selector หากข้อมูลที่เลือกไม่เปลี่ยนแปลง (โดยใช้ความเท่าเทียมกันอย่างเคร่งครัด ===
) คอมโพเนนต์จะไม่ re-render
วิธีการทำงาน
- กำหนด Context: สร้าง React Context โดยใช้
React.createContext()
- สร้าง Provider: ห่อแอปพลิเคชันหรือส่วนที่เกี่ยวข้องของคุณด้วย Context Provider เพื่อให้ค่า Context พร้อมใช้งานสำหรับลูก ๆ
- ใช้ Selectors: กำหนดฟังก์ชัน selector ที่ดึงข้อมูลเฉพาะจากค่า Context ฟังก์ชันเหล่านี้เป็นแบบ pure และควรคืนค่าเฉพาะข้อมูลที่จำเป็นเท่านั้น
- ใช้ Selector: ใช้ custom hook (หรือไลบรารี) ที่ใช้ประโยชน์จาก
useContext
และฟังก์ชัน selector ของคุณเพื่อดึงข้อมูลที่เลือกและสมัครรับการเปลี่ยนแปลงเฉพาะในข้อมูลนั้น
การใช้รูปแบบ Context Selector
ไลบรารีและการใช้งานแบบกำหนดเองหลายอย่างสามารถอำนวยความสะดวกให้กับรูปแบบ Context Selector ลองสำรวจแนวทางทั่วไปโดยใช้ custom hook
ตัวอย่าง: Context ผู้ใช้แบบง่าย
พิจารณา user context ที่มีโครงสร้างดังต่อไปนี้:
const UserContext = React.createContext({
name: 'John Doe',
email: 'john.doe@example.com',
country: 'USA',
language: 'en',
theme: 'light'
});
1. การสร้าง Context
const UserContext = React.createContext({
name: 'John Doe',
email: 'john.doe@example.com',
country: 'USA',
language: 'en',
theme: 'light'
});
2. การสร้าง Provider
const UserProvider = ({ children }) => {
const [user, setUser] = React.useState({
name: 'John Doe',
email: 'john.doe@example.com',
country: 'USA',
language: 'en',
theme: 'light'
});
const updateUser = (updates) => {
setUser(prevUser => ({ ...prevUser, ...updates }));
};
const value = React.useMemo(() => ({ user, updateUser }), [user]);
return (
{children}
);
};
3. การสร้าง Custom Hook ด้วย Selector
import React from 'react';
function useUserContext() {
const context = React.useContext(UserContext);
if (!context) {
throw new Error('useUserContext must be used within a UserProvider');
}
return context;
}
function useUserSelector(selector) {
const context = useUserContext();
const [selected, setSelected] = React.useState(() => selector(context.user));
React.useEffect(() => {
setSelected(selector(context.user)); // Initial selection
const unsubscribe = context.updateUser;
return () => {}; // No actual unsubscription needed in this simple example, see below for memoizing.
}, [context.user, selector]);
return selected;
}
หมายเหตุสำคัญ: `useEffect` ด้านบนขาดการ memoization ที่เหมาะสม เมื่อ `context.user` เปลี่ยนแปลง มันจะรันซ้ำ *เสมอ* แม้ว่าค่าที่เลือกจะเหมือนกันก็ตาม สำหรับ selector ที่แข็งแกร่งและ memoized โปรดดูส่วนถัดไปหรือไลบรารีเช่น `use-context-selector`
4. การใช้ Selector Hook ในคอมโพเนนต์
function UserName() {
const name = useUserSelector(user => user.name);
return Name: {name}
;
}
function UserEmail() {
const email = useUserSelector(user => user.email);
return Email: {email}
;
}
function UserCountry() {
const country = useUserSelector(user => user.country);
return Country: {country}
;
}
ในตัวอย่างนี้ คอมโพเนนต์ UserName
, UserEmail
และ UserCountry
จะ re-render เฉพาะเมื่อข้อมูลเฉพาะที่พวกเขาเลือก (ชื่อ, อีเมล, ประเทศ ตามลำดับ) เปลี่ยนแปลง หากการตั้งค่าภาษาของผู้ใช้ได้รับการอัปเดต คอมโพเนนต์เหล่านี้จะ *ไม่* re-render ซึ่งนำไปสู่การปรับปรุงประสิทธิภาพที่สำคัญ
การ Memoizing Selectors และ Values: สิ่งจำเป็นสำหรับการเพิ่มประสิทธิภาพ
เพื่อให้รูปแบบ Context Selector มีประสิทธิภาพอย่างแท้จริง การ memoization เป็นสิ่งสำคัญ หากไม่มี การทำงาน selector อาจคืนค่าอ็อบเจ็กต์หรืออาร์เรย์ใหม่แม้ว่าข้อมูลพื้นฐานจะไม่เปลี่ยนแปลงในเชิงความหมาย ซึ่งนำไปสู่การ re-render ที่ไม่จำเป็น ในทำนองเดียวกัน การตรวจสอบให้แน่ใจว่าค่า provider ก็ได้รับการ memoized ก็มีความสำคัญเช่นกัน
การ Memoizing ค่า Provider ด้วย useMemo
Hook useMemo
สามารถใช้เพื่อ memoize ค่าที่ส่งไปยัง UserContext.Provider
สิ่งนี้ทำให้มั่นใจได้ว่าค่า provider จะเปลี่ยนแปลงเฉพาะเมื่อ dependencies พื้นฐานเปลี่ยนแปลง
const UserProvider = ({ children }) => {
const [user, setUser] = React.useState({
name: 'John Doe',
email: 'john.doe@example.com',
country: 'USA',
language: 'en',
theme: 'light'
});
const updateUser = (updates) => {
setUser(prevUser => ({ ...prevUser, ...updates }));
};
// Memoize the value passed to the provider
const value = React.useMemo(() => ({
user,
updateUser
}), [user, updateUser]);
return (
{children}
);
};
การ Memoizing Selectors ด้วย useCallback
หากฟังก์ชัน selector ถูกกำหนด inline ภายในคอมโพเนนต์ ฟังก์ชันเหล่านั้นจะถูกสร้างขึ้นใหม่ในทุก render แม้ว่าจะเป็นฟังก์ชันเดียวกันในเชิงตรรกะก็ตาม สิ่งนี้สามารถเอาชนะจุดประสงค์ของรูปแบบ Context Selector ได้ เพื่อป้องกันสิ่งนี้ ให้ใช้ hook useCallback
เพื่อ memoize ฟังก์ชัน selector
function UserName() {
// Memoize the selector function
const nameSelector = React.useCallback(user => user.name, []);
const name = useUserSelector(nameSelector);
return Name: {name}
;
}
การเปรียบเทียบเชิงลึกและโครงสร้างข้อมูลที่ไม่เปลี่ยนรูป
สำหรับสถานการณ์ที่ซับซ้อนมากขึ้น โดยที่ข้อมูลภายใน Context นั้นซ้อนกันอยู่ลึก ๆ หรือมีอ็อบเจ็กต์ที่เปลี่ยนแปลงได้ ให้พิจารณาใช้โครงสร้างข้อมูลที่ไม่เปลี่ยนรูป (เช่น Immutable.js, Immer) หรือใช้ฟังก์ชันเปรียบเทียบเชิงลึกใน selector ของคุณ สิ่งนี้ทำให้มั่นใจได้ว่าการเปลี่ยนแปลงจะถูกตรวจพบอย่างถูกต้อง แม้ว่าอ็อบเจ็กต์พื้นฐานจะถูกเปลี่ยนแปลงในสถานที่ก็ตาม
ไลบรารีสำหรับรูปแบบ Context Selector
ไลบรารีหลายแห่งมีโซลูชันสำเร็จรูปสำหรับการใช้งานรูปแบบ Context Selector ทำให้กระบวนการง่ายขึ้นและนำเสนอคุณสมบัติเพิ่มเติม
use-context-selector
use-context-selector
เป็นไลบรารีที่ได้รับความนิยมและมีการดูแลรักษาอย่างดีซึ่งออกแบบมาโดยเฉพาะเพื่อจุดประสงค์นี้ นำเสนอวิธีที่ง่ายและมีประสิทธิภาพในการเลือกค่าเฉพาะจาก Context และป้องกันการ re-render ที่ไม่จำเป็น
การติดตั้ง:
npm install use-context-selector
การใช้งาน:
import { useContextSelector } from 'use-context-selector';
function UserName() {
const name = useContextSelector(UserContext, user => user.name);
return Name: {name}
;
}
Valtio
Valtio เป็นไลบรารีการจัดการสถานะที่ครอบคลุมมากขึ้น ซึ่งใช้พร็อกซีสำหรับการอัปเดตสถานะที่มีประสิทธิภาพและการ re-render แบบเลือกได้ นำเสนอแนวทางที่แตกต่างในการจัดการสถานะ แต่สามารถใช้เพื่อให้ได้ประโยชน์ด้านประสิทธิภาพที่คล้ายคลึงกับรูปแบบ Context Selector
ประโยชน์ของรูปแบบ Context Selector
- ประสิทธิภาพที่ดีขึ้น: ลดการ re-render ที่ไม่จำเป็น นำไปสู่แอปพลิเคชันที่ตอบสนองและมีประสิทธิภาพมากขึ้น
- ลดการใช้หน่วยความจำ: ป้องกันไม่ให้คอมโพเนนต์สมัครรับข้อมูลที่ไม่จำเป็น ลดการใช้หน่วยความจำ
- เพิ่มความสามารถในการบำรุงรักษา: ปรับปรุงความชัดเจนของโค้ดและความสามารถในการบำรุงรักษาโดยการกำหนด dependencies ของข้อมูลของแต่ละคอมโพเนนต์อย่างชัดเจน
- ความสามารถในการปรับขนาดที่ดีขึ้น: ทำให้ง่ายต่อการปรับขนาดแอปพลิเคชันของคุณเมื่อจำนวนคอมโพเนนต์และความซับซ้อนของสถานะเพิ่มขึ้น
เมื่อใดควรใช้รูปแบบ Context Selector
รูปแบบ Context Selector มีประโยชน์อย่างยิ่งในสถานการณ์ต่อไปนี้:
- ค่า Context ขนาดใหญ่: เมื่อ Context ของคุณจัดเก็บข้อมูลจำนวนมาก และคอมโพเนนต์ต้องการเพียงส่วนย่อย ๆ เท่านั้น
- การอัปเดต Context บ่อยครั้ง: เมื่อค่า Context ได้รับการอัปเดตบ่อยครั้ง และคุณต้องการลดการ re-render
- คอมโพเนนต์ที่สำคัญต่อประสิทธิภาพ: เมื่อคอมโพเนนต์บางตัวมีความไวต่อประสิทธิภาพ และคุณต้องการให้แน่ใจว่าคอมโพเนนต์เหล่านั้นจะ re-render เฉพาะเมื่อจำเป็นเท่านั้น
- แผนผังส่วนประกอบที่ซับซ้อน: ในแอปพลิเคชันที่มีแผนผังส่วนประกอบที่ลึก การ re-render ที่ไม่จำเป็นสามารถแพร่กระจายลงไปในแผนผังและส่งผลกระทบต่อประสิทธิภาพอย่างมาก ลองนึกภาพทีมงานที่กระจายอยู่ทั่วโลกที่ทำงานกับระบบการออกแบบที่ซับซ้อน การเปลี่ยนแปลงส่วนประกอบปุ่มในสถานที่หนึ่งอาจกระตุ้นให้เกิดการ re-render ทั่วทั้งระบบ ซึ่งส่งผลกระทบต่อนักพัฒนาในเขตเวลาอื่น ๆ
ทางเลือกอื่นสำหรับรูปแบบ Context Selector
แม้ว่ารูปแบบ Context Selector จะเป็นเครื่องมือที่มีประสิทธิภาพ แต่ก็ไม่ใช่ทางออกเดียวสำหรับการเพิ่มประสิทธิภาพการ re-render ใน React ต่อไปนี้เป็นแนวทางอื่น ๆ:
- Redux: Redux เป็นไลบรารีการจัดการสถานะยอดนิยมที่ใช้ store เดียวและการอัปเดตสถานะที่คาดการณ์ได้ นำเสนอการควบคุมการอัปเดตสถานะอย่างละเอียดและสามารถใช้เพื่อป้องกันการ re-render ที่ไม่จำเป็น
- MobX: MobX เป็นไลบรารีการจัดการสถานะอื่นที่ใช้ข้อมูลที่สังเกตได้และการติดตาม dependency อัตโนมัติ จะ re-render คอมโพเนนต์โดยอัตโนมัติเฉพาะเมื่อ dependencies เปลี่ยนแปลง
- Zustand: โซลูชันการจัดการสถานะแบบ barebones ขนาดเล็ก รวดเร็ว และปรับขนาดได้ โดยใช้หลักการ flux ที่เรียบง่าย
- Recoil: Recoil เป็นไลบรารีการจัดการสถานะทดลองจาก Facebook ที่ใช้อะตอมและ selector เพื่อให้การควบคุมการอัปเดตสถานะอย่างละเอียดและป้องกันการ re-render ที่ไม่จำเป็น
- การจัดองค์ประกอบของคอมโพเนนต์: ในบางกรณี คุณสามารถหลีกเลี่ยงการใช้สถานะส่วนกลางโดยสิ้นเชิงได้โดยการส่งข้อมูลลงไปตาม props ของคอมโพเนนต์ สิ่งนี้สามารถปรับปรุงประสิทธิภาพและทำให้สถาปัตยกรรมของแอปพลิเคชันของคุณง่ายขึ้น
ข้อควรพิจารณาสำหรับแอปพลิเคชันระดับโลก
เมื่อพัฒนาแอปพลิเคชันสำหรับผู้ชมทั่วโลก ให้พิจารณาปัจจัยต่อไปนี้เมื่อใช้งานรูปแบบ Context Selector:
- Internationalization (i18n): หากแอปพลิเคชันของคุณรองรับหลายภาษา ตรวจสอบให้แน่ใจว่า Context ของคุณจัดเก็บการตั้งค่าภาษาของผู้ใช้และคอมโพเนนต์ของคุณ re-render เมื่อภาษาเปลี่ยนแปลง อย่างไรก็ตาม ให้ใช้รูปแบบ Context Selector เพื่อป้องกันไม่ให้คอมโพเนนต์อื่น ๆ re-render โดยไม่จำเป็น ตัวอย่างเช่น คอมโพเนนต์แปลงสกุลเงินอาจต้อง re-render เฉพาะเมื่อตำแหน่งที่ตั้งของผู้ใช้เปลี่ยนแปลง ซึ่งส่งผลต่อสกุลเงินเริ่มต้น
- Localization (l10n): พิจารณาความแตกต่างทางวัฒนธรรมในการจัดรูปแบบข้อมูล (เช่น รูปแบบวันที่และเวลา รูปแบบตัวเลข) ใช้ Context เพื่อจัดเก็บการตั้งค่า localization และตรวจสอบให้แน่ใจว่าคอมโพเนนต์ของคุณแสดงข้อมูลตาม locale ของผู้ใช้ อีกครั้ง ใช้รูปแบบ selector
- Time Zones: หากแอปพลิเคชันของคุณแสดงข้อมูลที่ละเอียดอ่อนต่อเวลา ให้จัดการ time zones อย่างถูกต้อง ใช้ Context เพื่อจัดเก็บ time zone ของผู้ใช้และตรวจสอบให้แน่ใจว่าคอมโพเนนต์ของคุณแสดงเวลาในเวลาท้องถิ่นของผู้ใช้
- Accessibility (a11y): ตรวจสอบให้แน่ใจว่าแอปพลิเคชันของคุณสามารถเข้าถึงได้สำหรับผู้ใช้ที่มีความพิการ ใช้ Context เพื่อจัดเก็บการตั้งค่า accessibility (เช่น ขนาดตัวอักษร ความคมชัดของสี) และตรวจสอบให้แน่ใจว่าคอมโพเนนต์ของคุณเคารพการตั้งค่าเหล่านี้
สรุป
รูปแบบ React Context Selector เป็นเทคนิคที่มีค่าสำหรับการเพิ่มประสิทธิภาพการ re-render และปรับปรุงประสิทธิภาพในแอปพลิเคชัน React การอนุญาตให้คอมโพเนนต์สมัครรับเฉพาะส่วนที่ต้องการของ Context เท่านั้น คุณสามารถลดการ re-render ที่ไม่จำเป็นได้อย่างมากและสร้างอินเทอร์เฟซผู้ใช้ที่ตอบสนองและมีประสิทธิภาพมากขึ้น อย่าลืม memoize selector และค่า provider ของคุณเพื่อการเพิ่มประสิทธิภาพสูงสุด พิจารณาไลบรารีเช่น use-context-selector
เพื่อลดความซับซ้อนในการใช้งาน เมื่อคุณสร้างแอปพลิเคชันที่ซับซ้อนมากขึ้น การทำความเข้าใจและการใช้เทคนิคเช่นรูปแบบ Context Selector จะมีความสำคัญต่อการรักษาประสิทธิภาพและมอบประสบการณ์การใช้งานที่ยอดเยี่ยม โดยเฉพาะอย่างยิ่งสำหรับผู้ชมทั่วโลก