ไทย

สำรวจ React useEvent hook ซึ่งเป็นเครื่องมืออันทรงพลังสำหรับสร้างการอ้างอิง event handler ที่เสถียรในแอปพลิเคชัน React แบบไดนามิก ช่วยเพิ่มประสิทธิภาพและป้องกันการ re-render ที่ไม่จำเป็น

React useEvent: การสร้างการอ้างอิง Event Handler ที่เสถียร

นักพัฒนา React มักจะประสบกับความท้าทายในการจัดการกับ event handler โดยเฉพาะในสถานการณ์ที่เกี่ยวข้องกับคอมโพเนนต์แบบไดนามิกและ closures useEvent hook ซึ่งเป็นส่วนเสริมที่ค่อนข้างใหม่ในระบบนิเวศของ React ได้มอบทางออกที่สง่างามสำหรับปัญหาเหล่านี้ ทำให้นักพัฒนาสามารถสร้างการอ้างอิง event handler ที่เสถียรซึ่งไม่ก่อให้เกิดการ re-render ที่ไม่จำเป็น

ทำความเข้าใจปัญหา: ความไม่เสถียรของ Event Handlers

ใน React คอมโพเนนต์จะ re-render เมื่อ props หรือ state ของมันเปลี่ยนแปลง เมื่อฟังก์ชัน event handler ถูกส่งผ่านเป็น prop อินสแตนซ์ของฟังก์ชันใหม่มักจะถูกสร้างขึ้นทุกครั้งที่คอมโพเนนต์แม่ (parent component) ทำการ render อินสแตนซ์ของฟังก์ชันใหม่นี้ แม้ว่าจะมี logic เหมือนเดิม แต่ React จะถือว่าเป็นคนละตัวกัน ซึ่งนำไปสู่การ re-render ของคอมโพเนนต์ลูก (child component) ที่รับค่า prop นั้นไป

ลองพิจารณาตัวอย่างง่ายๆ นี้:


import React, { useState } from 'react';

function ParentComponent() {
  const [count, setCount] = useState(0);

  const handleClick = () => {
    console.log('Clicked from Parent:', count);
    setCount(count + 1);
  };

  return (
    

Count: {count}

); } function ChildComponent({ onClick }) { console.log('ChildComponent rendered'); return ; } export default ParentComponent;

ในตัวอย่างนี้ handleClick จะถูกสร้างขึ้นใหม่ทุกครั้งที่ ParentComponent ทำการ render แม้ว่า ChildComponent อาจจะถูกปรับให้เหมาะสมแล้ว (เช่น ใช้ React.memo) มันก็จะยังคง re-render อยู่ดี เพราะ prop onClick ได้เปลี่ยนไปแล้ว ซึ่งอาจนำไปสู่ปัญหาด้านประสิทธิภาพ โดยเฉพาะในแอปพลิเคชันที่ซับซ้อน

ขอแนะนำ useEvent: ทางออกของปัญหา

useEvent hook แก้ปัญหานี้โดยการให้การอ้างอิงที่เสถียรไปยังฟังก์ชัน event handler มันช่วยแยก event handler ออกจากวงจรการ re-render ของคอมโพเนนต์แม่ได้อย่างมีประสิทธิภาพ

ถึงแม้ว่า useEvent จะไม่ใช่ hook ที่มีมาให้ในตัวของ React (ณ React 18) แต่มันสามารถถูกสร้างขึ้นมาเป็น custom hook ได้อย่างง่ายดาย หรือในบาง framework และ library ก็มีให้เป็นส่วนหนึ่งของชุดเครื่องมือของพวกเขา นี่คือการสร้าง useEvent ที่พบได้บ่อย:


import { useCallback, useRef, useLayoutEffect } from 'react';

function useEvent any>(fn: T): T {
  const ref = useRef(fn);

  // UseLayoutEffect is crucial here for synchronous updates
  useLayoutEffect(() => {
    ref.current = fn;
  });

  return useCallback(
    (...args: Parameters): ReturnType => {
      return ref.current(...args);
    },
    [] // The dependency array is intentionally empty, ensuring stability
  ) as T;
}

export default useEvent;

คำอธิบาย:

การใช้ useEvent ในทางปฏิบัติ

ตอนนี้ เรามา refactor ตัวอย่างก่อนหน้านี้โดยใช้ useEvent กัน:


import React, { useState, useCallback, useRef, useLayoutEffect } from 'react';

function useEvent any>(fn: T): T {
  const ref = useRef(fn);

  // UseLayoutEffect is crucial here for synchronous updates
  useLayoutEffect(() => {
    ref.current = fn;
  });

  return useCallback(
    (...args: Parameters): ReturnType => {
      return ref.current(...args);
    },
    [] // The dependency array is intentionally empty, ensuring stability
  ) as T;
}

function ParentComponent() {
  const [count, setCount] = useState(0);

  const handleClick = useEvent(() => {
    console.log('Clicked from Parent:', count);
    setCount(count + 1);
  });

  return (
    

Count: {count}

); } function ChildComponent({ onClick }) { console.log('ChildComponent rendered'); return ; } export default ParentComponent;

ด้วยการครอบ handleClick ด้วย useEvent เรามั่นใจได้ว่า ChildComponent จะได้รับการอ้างอิงฟังก์ชันเดียวกันตลอดการ render ของ ParentComponent แม้ว่า state ของ count จะเปลี่ยนแปลงก็ตาม ซึ่งจะช่วยป้องกันการ re-render ที่ไม่จำเป็นของ ChildComponent

ประโยชน์ของการใช้ useEvent

กรณีการใช้งานสำหรับ useEvent

ทางเลือกและข้อควรพิจารณา

แม้ว่า useEvent จะเป็นเครื่องมือที่ทรงพลัง แต่ก็มีแนวทางทางเลือกและข้อควรพิจารณาที่ต้องจำไว้:

ข้อควรพิจารณาด้าน Internationalization และ Accessibility

เมื่อพัฒนาแอปพลิเคชัน React สำหรับผู้ใช้ทั่วโลก การพิจารณาเรื่อง internationalization (i18n) และ accessibility (a11y) เป็นสิ่งสำคัญ useEvent เองไม่ได้ส่งผลโดยตรงต่อ i18n หรือ a11y แต่มันสามารถปรับปรุงประสิทธิภาพของคอมโพเนนต์ที่จัดการกับเนื้อหาที่แปลเป็นภาษาท้องถิ่นหรือฟีเจอร์ด้าน accessibility ได้โดยอ้อม

ตัวอย่างเช่น หากคอมโพเนนต์แสดงข้อความที่แปลเป็นภาษาท้องถิ่นหรือใช้ ARIA attributes ตามภาษาปัจจุบัน การทำให้แน่ใจว่า event handler ภายในคอมโพเนนต์นั้นมีความเสถียรจะสามารถป้องกันการ re-render ที่ไม่จำเป็นเมื่อมีการเปลี่ยนแปลงภาษาได้

ตัวอย่าง: useEvent กับ Localization


import React, { useState, useContext, createContext, useCallback, useRef, useLayoutEffect } from 'react';

function useEvent any>(fn: T): T {
  const ref = useRef(fn);

  // UseLayoutEffect is crucial here for synchronous updates
  useLayoutEffect(() => {
    ref.current = fn;
  });

  return useCallback(
    (...args: Parameters): ReturnType => {
      return ref.current(...args);
    },
    [] // The dependency array is intentionally empty, ensuring stability
  ) as T;
}

const LanguageContext = createContext('en');

function LocalizedButton() {
  const language = useContext(LanguageContext);
  const [text, setText] = useState(getLocalizedText(language));

  const handleClick = useEvent(() => {
    console.log('Button clicked in', language);
    // Perform some action based on the language
  });

  function getLocalizedText(lang) {
      switch (lang) {
        case 'en':
          return 'Click me';
        case 'fr':
          return 'Cliquez ici';
        case 'es':
          return 'Haz clic aquí';
        default:
          return 'Click me';
      }
    }

    //Simulate language change
    React.useEffect(()=>{
        setTimeout(()=>{
            setText(getLocalizedText(language === 'en' ? 'fr' : 'en'))
        }, 2000)
    }, [language])

  return ;
}

function App() {
  const [language, setLanguage] = useState('en');

  const toggleLanguage = useCallback(() => {
    setLanguage(language === 'en' ? 'fr' : 'en');
  }, [language]);

  return (
    
      
); } export default App;

ในตัวอย่างนี้ คอมโพเนนต์ LocalizedButton จะแสดงข้อความตามภาษาปัจจุบัน โดยการใช้ useEvent สำหรับ handler handleClick เรามั่นใจได้ว่าปุ่มจะไม่ re-render โดยไม่จำเป็นเมื่อมีการเปลี่ยนภาษา ซึ่งจะช่วยปรับปรุงประสิทธิภาพและประสบการณ์ของผู้ใช้

สรุป

useEvent hook เป็นเครื่องมือที่มีค่าสำหรับนักพัฒนา React ที่ต้องการเพิ่มประสิทธิภาพและทำให้ logic ของคอมโพเนนต์ง่ายขึ้น ด้วยการให้การอ้างอิง event handler ที่เสถียร มันช่วยป้องกันการ re-render ที่ไม่จำเป็น ปรับปรุงความสามารถในการอ่านโค้ด และเพิ่มประสิทธิภาพโดยรวมของแอปพลิเคชัน React แม้ว่ามันจะไม่ใช่ hook ที่มีมาให้ในตัวของ React แต่การสร้างที่ตรงไปตรงมาและประโยชน์ที่สำคัญทำให้มันเป็นส่วนเสริมที่คุ้มค่าสำหรับชุดเครื่องมือของนักพัฒนา React ทุกคน

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