คู่มือฉบับสมบูรณ์เกี่ยวกับ React forwardRef ครอบคลุมวัตถุประสงค์ การนำไปใช้ กรณีการใช้งาน และแนวทางปฏิบัติที่ดีที่สุดเพื่อสร้างคอมโพเนนต์ React ที่นำกลับมาใช้ซ้ำและดูแลรักษาง่าย
React forwardRef: การเชี่ยวชาญ Ref Forwarding เพื่อสร้างคอมโพเนนต์ที่นำกลับมาใช้ใหม่ได้
ในโลกของ React การสร้างคอมโพเนนต์ที่สามารถนำกลับมาใช้ใหม่และประกอบกันได้ถือเป็นสิ่งสำคัญยิ่ง อย่างไรก็ตาม บางครั้งคุณจำเป็นต้องเข้าถึง DOM node ของ child component จาก parent ของมัน นี่คือจุดที่ React.forwardRef
เข้ามาช่วย คู่มือฉบับสมบูรณ์นี้จะเจาะลึกรายละเอียดของ forwardRef
โดยอธิบายถึงวัตถุประสงค์ การนำไปใช้ กรณีการใช้งาน และแนวทางปฏิบัติที่ดีที่สุด
Ref Forwarding คืออะไร?
Ref forwarding เป็นเทคนิคใน React ที่ช่วยให้ parent component สามารถเข้าถึง DOM node หรือ instance ของ React component ที่เป็น child component ได้ โดยพื้นฐานแล้ว มันจะ "ส่งต่อ" (forward) ref ที่ส่งไปยังคอมโพเนนต์หนึ่งลงไปยัง child ของมัน สิ่งนี้มีประโยชน์อย่างยิ่งเมื่อคุณต้องการจัดการ DOM ของ child component โดยตรง เช่น การโฟกัสที่ input field หรือการวัดขนาดของมัน
หากไม่มี forwardRef
ref จะสามารถแนบได้โดยตรงกับ DOM element หรือ class component เท่านั้น Functional component ไม่สามารถรับหรือเปิดเผย ref ได้โดยตรง
ทำไมต้องใช้ forwardRef
?
มีหลายสถานการณ์ที่จำเป็นต้องใช้ forwardRef
:
- การจัดการ DOM: เมื่อคุณต้องการโต้ตอบกับ DOM ของ child component โดยตรง ตัวอย่างเช่น การตั้งค่าโฟกัสบน input field, การสั่งงานแอนิเมชัน หรือการวัดขนาด element
- การสร้าง Abstraction: เมื่อสร้างคอมโพเนนต์ UI ที่ใช้ซ้ำได้ ซึ่งจำเป็นต้องเปิดเผย DOM element บางอย่างเพื่อให้สามารถปรับแต่งหรือนำไปรวมกับส่วนอื่น ๆ ของแอปพลิเคชันได้
- Higher-Order Components (HOCs): เมื่อครอบคอมโพเนนต์ด้วย HOC และต้องการให้แน่ใจว่า ref ถูกส่งผ่านไปยังคอมโพเนนต์ที่อยู่ภายใต้ได้อย่างถูกต้อง
- ไลบรารีคอมโพเนนต์: เมื่อสร้างไลบรารีคอมโพเนนต์ การส่งต่อ ref ช่วยให้นักพัฒนาสามารถเข้าถึงและปรับแต่ง DOM element พื้นฐานของคอมโพเนนต์ของคุณได้ ซึ่งให้ความยืดหยุ่นที่มากขึ้น
forwardRef
ทำงานอย่างไร
React.forwardRef
เป็น Higher-Order Component (HOC) ที่รับฟังก์ชันการเรนเดอร์เป็นอาร์กิวเมนต์ ฟังก์ชันการเรนเดอร์นี้จะได้รับ props
และ ref
เป็นอาร์กิวเมนต์ จากนั้นฟังก์ชันการเรนเดอร์จะส่งคืน React element อาร์กิวเมนต์ ref
คือ ref ที่ถูกส่งมาจาก parent component คุณสามารถนำ ref นี้ไปแนบกับ child component ภายในฟังก์ชันการเรนเดอร์ได้
นี่คือขั้นตอนการทำงานโดยสรุป:
- Parent component สร้าง ref โดยใช้
useRef
- Parent component ส่ง ref ไปยัง child component ในรูปแบบของ prop
- Child component ถูกครอบด้วย
React.forwardRef
- ภายในฟังก์ชันการเรนเดอร์ของ
forwardRef
ref จะถูกแนบเข้ากับ DOM element หรือ React component อื่น - ตอนนี้ Parent component สามารถเข้าถึง DOM node หรือ instance ของคอมโพเนนต์ผ่าน ref นั้นได้
การนำ forwardRef
ไปใช้: ตัวอย่างการใช้งานจริง
เรามาดูตัวอย่างง่ายๆ ของ forwardRef
กับคอมโพเนนต์ input ที่กำหนดเอง ซึ่งอนุญาตให้ parent component สามารถโฟกัส input field ได้โดยใช้โค้ด
ตัวอย่าง: คอมโพเนนต์ Input ที่กำหนดเองพร้อม Ref Forwarding
ขั้นแรก เรามาสร้างคอมโพเนนต์ input ที่กำหนดเอง:
import React, { forwardRef } from 'react';
const CustomInput = forwardRef((props, ref) => {
return (
<div>
<label htmlFor={props.id}>{props.label}</label>
<input type="text" id={props.id} ref={ref} {...props} />
</div>
);
});
CustomInput.displayName = "CustomInput"; // แนะนำให้ใส่เพื่อการดีบักที่ดีขึ้น
export default CustomInput;
ในตัวอย่างนี้:
- เรา import
forwardRef
จาก 'react' - เราครอบ functional component ของเราด้วย
forwardRef
- ฟังก์ชัน
forwardRef
ได้รับprops
และref
เป็นอาร์กิวเมนต์ - เราแนบ
ref
เข้ากับ element<input>
- เราตั้งค่า
displayName
เพื่อการดีบักที่ดีขึ้นใน React DevTools
ตอนนี้ เรามาดูวิธีการใช้คอมโพเนนต์นี้ใน parent component:
import React, { useRef, useEffect } from 'react';
import CustomInput from './CustomInput';
const ParentComponent = () => {
const inputRef = useRef(null);
useEffect(() => {
// โฟกัสที่ input field เมื่อคอมโพเนนต์ถูก mount
if (inputRef.current) {
inputRef.current.focus();
}
}, []);
return (
<div>
<CustomInput label="Name:" id="name" ref={inputRef} placeholder="Enter your name" />
</div>
);
};
export default ParentComponent;
ใน parent component นี้:
- เราสร้าง ref โดยใช้
useRef
- เราส่ง
inputRef
ไปยังคอมโพเนนต์CustomInput
เป็นref
prop - ภายใน
useEffect
hook เราเข้าถึง DOM node ที่อยู่เบื้องหลังโดยใช้inputRef.current
และเรียกใช้เมธอดfocus()
เมื่อ ParentComponent
ถูก mount ช่อง input ในคอมโพเนนต์ CustomInput
จะถูกโฟกัสโดยอัตโนมัติ
กรณีการใช้งานของ forwardRef
นี่คือกรณีการใช้งานทั่วไปที่ forwardRef
พิสูจน์ให้เห็นถึงคุณค่าอย่างมหาศาล:
1. การโฟกัส Input Fields
ดังที่แสดงในตัวอย่างข้างต้น forwardRef
ช่วยให้คุณสามารถโฟกัส input field โดยใช้โค้ดได้ ซึ่งมีประโยชน์สำหรับการตรวจสอบความถูกต้องของฟอร์ม (form validation) การเข้าถึง (accessibility) และการปรับปรุงประสบการณ์ผู้ใช้ ตัวอย่างเช่น หลังจากผู้ใช้ส่งฟอร์มที่มีข้อผิดพลาด คุณสามารถโฟกัสที่ input field แรกที่มีข้อผิดพลาดเพื่อนำทางผู้ใช้
2. การวัดขนาด Element
คุณสามารถใช้ forwardRef
เพื่อเข้าถึง DOM node ของ child component และวัดขนาดของมัน (ความกว้าง ความสูง ฯลฯ) ซึ่งมีประโยชน์สำหรับการสร้าง responsive layout, การปรับขนาดแบบไดนามิก และแอนิเมชันที่กำหนดเอง คุณอาจต้องวัดความสูงของพื้นที่เนื้อหาแบบไดนามิกเพื่อปรับเลย์เอาต์ของ element อื่น ๆ บนหน้าเว็บ
3. การทำงานร่วมกับไลบรารีของบุคคลที่สาม
ไลบรารีของบุคคลที่สามหลายแห่งต้องการการเข้าถึง DOM node โดยตรงเพื่อเริ่มต้นการทำงานหรือตั้งค่า forwardRef
ช่วยให้คุณสามารถผสานรวมไลบรารีเหล่านี้เข้ากับคอมโพเนนต์ React ของคุณได้อย่างราบรื่น ลองนึกภาพการใช้ไลบรารีสร้างกราฟที่ต้องการ DOM element เป็นเป้าหมายในการเรนเดอร์กราฟ forwardRef
ช่วยให้คุณสามารถส่ง DOM element นั้นไปยังไลบรารีได้
4. การสร้างคอมโพเนนต์ที่เข้าถึงได้ง่าย (Accessible Components)
การเข้าถึงได้ง่าย (Accessibility) มักต้องการการจัดการแอตทริบิวต์ของ DOM หรือการจัดการโฟกัสโดยตรง forwardRef
สามารถใช้เพื่อสร้างคอมโพเนนต์ที่เข้าถึงได้ง่ายซึ่งสอดคล้องกับมาตรฐานการเข้าถึง ตัวอย่างเช่น คุณอาจต้องตั้งค่าแอตทริบิวต์ aria-describedby
บน input field เพื่อเชื่อมโยงกับข้อความแสดงข้อผิดพลาด ซึ่งสิ่งนี้ต้องการการเข้าถึง DOM node ของ input field โดยตรง
5. Higher-Order Components (HOCs)
เมื่อสร้าง HOCs สิ่งสำคัญคือต้องแน่ใจว่า ref ถูกส่งผ่านไปยังคอมโพเนนต์ที่ถูกครอบได้อย่างถูกต้อง forwardRef
ช่วยให้คุณบรรลุเป้าหมายนี้ได้ สมมติว่าคุณมี HOC ที่เพิ่มสไตล์ให้กับคอมโพเนนต์ การใช้ forwardRef
จะช่วยให้แน่ใจว่า ref ใด ๆ ที่ส่งไปยังคอมโพเนนต์ที่จัดสไตล์แล้ว จะถูกส่งต่อไปยังคอมโพเนนต์ที่อยู่ภายใต้
แนวทางปฏิบัติที่ดีที่สุดสำหรับการใช้ forwardRef
เพื่อให้แน่ใจว่าคุณใช้ forwardRef
ได้อย่างมีประสิทธิภาพ ให้พิจารณาแนวทางปฏิบัติที่ดีที่สุดเหล่านี้:
1. ใช้ displayName
เพื่อการดีบัก
ตั้งค่า property displayName
บนคอมโพเนนต์ forwardRef
ของคุณเสมอ ซึ่งจะช่วยให้ระบุคอมโพเนนต์เหล่านั้นใน React DevTools ได้ง่ายขึ้น ตัวอย่างเช่น:
CustomInput.displayName = "CustomInput";
2. คำนึงถึงประสิทธิภาพ
แม้ว่า forwardRef
จะเป็นเครื่องมือที่ทรงพลัง แต่ก็อาจส่งผลกระทบต่อประสิทธิภาพได้หากใช้มากเกินไป หลีกเลี่ยงการจัดการ DOM ที่ไม่จำเป็นและปรับปรุงตรรกะการเรนเดอร์ของคุณให้เหมาะสม ใช้เครื่องมือ profile เพื่อระบุปัญหาคอขวดด้านประสิทธิภาพที่เกี่ยวข้องกับการส่งต่อ ref
3. ใช้ Ref อย่างรอบคอบ
อย่าใช้ ref แทนที่การไหลของข้อมูลของ React ควรใช้ ref เท่าที่จำเป็นและเฉพาะเมื่อจำเป็นสำหรับการจัดการ DOM หรือการทำงานร่วมกับไลบรารีของบุคคลที่สามเท่านั้น พึ่งพา props และ state ในการจัดการข้อมูลและพฤติกรรมของคอมโพเนนต์
4. จัดทำเอกสารสำหรับคอมโพเนนต์ของคุณ
ระบุอย่างชัดเจนว่าคุณใช้ forwardRef
เมื่อใดและทำไมในคอมโพเนนต์ของคุณ สิ่งนี้ช่วยให้นักพัฒนาคนอื่นเข้าใจโค้ดของคุณและใช้คอมโพเนนต์ของคุณได้อย่างถูกต้อง รวมตัวอย่างวิธีการใช้คอมโพเนนต์และวัตถุประสงค์ของ ref ที่ส่งต่อ
5. พิจารณาทางเลือกอื่น
ก่อนที่จะใช้ forwardRef
ให้พิจารณาว่ามีวิธีแก้ปัญหาอื่นที่อาจเหมาะสมกว่าหรือไม่ ตัวอย่างเช่น คุณอาจสามารถบรรลุพฤติกรรมที่ต้องการได้โดยใช้ props และ state แทนการจัดการ DOM โดยตรง สำรวจตัวเลือกอื่น ๆ ก่อนที่จะหันไปใช้ forwardRef
ทางเลือกอื่นนอกเหนือจาก forwardRef
แม้ว่า forwardRef
มักจะเป็นทางออกที่ดีที่สุดสำหรับการส่งต่อ ref แต่ก็มีแนวทางอื่นที่คุณอาจพิจารณาในบางสถานการณ์:
1. Callback Refs
Callback refs เป็นวิธีที่ยืดหยุ่นกว่าในการเข้าถึง DOM node แทนที่จะส่ง ref
prop คุณจะส่งฟังก์ชันที่รับ DOM node เป็นอาร์กิวเมนต์ สิ่งนี้ช่วยให้คุณสามารถดำเนินการตรรกะที่กำหนดเองได้เมื่อ DOM node ถูกแนบหรือถอดออก อย่างไรก็ตาม callback refs อาจทำให้โค้ดยาวและอ่านยากกว่า forwardRef
const MyComponent = () => {
let inputElement = null;
const setInputElement = (element) => {
inputElement = element;
};
useEffect(() => {
if (inputElement) {
inputElement.focus();
}
}, []);
return <input type="text" ref={setInputElement} />;
};
2. Composition (การประกอบ)
ในบางกรณี คุณสามารถบรรลุพฤติกรรมที่ต้องการได้โดยการประกอบคอมโพเนนต์เข้าด้วยกันแทนที่จะใช้ forwardRef
ซึ่งเกี่ยวข้องกับการแบ่งคอมโพเนนต์ที่ซับซ้อนออกเป็นคอมโพเนนต์ขนาดเล็กที่จัดการได้ง่ายขึ้น และส่งข้อมูลและพฤติกรรมระหว่างกันโดยใช้ props การประกอบสามารถนำไปสู่โค้ดที่ดูแลรักษาและทดสอบได้ง่ายขึ้น แต่อาจไม่เหมาะสำหรับทุกสถานการณ์
3. Render Props
Render props ช่วยให้คุณสามารถแชร์โค้ดระหว่างคอมโพเนนต์ React โดยใช้ prop ที่มีค่าเป็นฟังก์ชัน คุณสามารถใช้ render props เพื่อเปิดเผย DOM node หรือ instance ของคอมโพเนนต์ไปยัง parent component ได้ อย่างไรก็ตาม render props อาจทำให้โค้ดของคุณซับซ้อนและอ่านยากขึ้น โดยเฉพาะเมื่อต้องจัดการกับ render props หลายตัว
ข้อผิดพลาดทั่วไปที่ควรหลีกเลี่ยง
เมื่อทำงานกับ forwardRef
สิ่งสำคัญคือต้องหลีกเลี่ยงข้อผิดพลาดทั่วไปเหล่านี้:
1. ลืมตั้งค่า displayName
ดังที่ได้กล่าวไปแล้ว การลืมตั้งค่า property displayName
อาจทำให้การดีบักทำได้ยาก ตั้งค่า displayName
สำหรับคอมโพเนนต์ forwardRef
ของคุณเสมอ
2. การใช้ Ref มากเกินไป
ต่อต้านความอยากที่จะใช้ ref สำหรับทุกสิ่งทุกอย่าง ควรใช้ ref เท่าที่จำเป็นและเฉพาะเมื่อจำเป็นสำหรับการจัดการ DOM หรือการทำงานร่วมกับไลบรารีของบุคคลที่สามเท่านั้น พึ่งพา props และ state ในการจัดการข้อมูลและพฤติกรรมของคอมโพเนนต์
3. การจัดการ DOM โดยตรงโดยไม่มีเหตุผลอันควร
การจัดการ DOM โดยตรงอาจทำให้โค้ดของคุณดูแลรักษาและทดสอบได้ยากขึ้น จัดการ DOM เฉพาะเมื่อจำเป็นอย่างยิ่งและหลีกเลี่ยงการอัปเดต DOM ที่ไม่จำเป็น
4. ไม่จัดการกับ Ref ที่เป็น Null
ตรวจสอบเสมอว่า ref เป็น null หรือไม่ก่อนที่จะเข้าถึง DOM node หรือ instance ของคอมโพเนนต์ที่อยู่ภายใต้ สิ่งนี้จะช่วยป้องกันข้อผิดพลาดเมื่อคอมโพเนนต์ยังไม่ถูก mount หรือถูก unmount ไปแล้ว
if (inputRef.current) {
inputRef.current.focus();
}
5. การสร้าง Circular Dependencies
ระมัดระวังเมื่อใช้ forwardRef
ร่วมกับเทคนิคอื่น ๆ เช่น HOCs หรือ render props หลีกเลี่ยงการสร้างการพึ่งพากันเป็นวงกลม (circular dependencies) ระหว่างคอมโพเนนต์ เนื่องจากอาจนำไปสู่ปัญหาด้านประสิทธิภาพหรือพฤติกรรมที่ไม่คาดคิด
ตัวอย่างจากทั่วโลก
หลักการของ React และ forwardRef
เป็นสากล แต่ลองพิจารณาว่านักพัฒนาจากภูมิภาคต่าง ๆ อาจปรับใช้มันอย่างไร:
- Localization และ Internationalization (i18n): นักพัฒนาที่สร้างแอปพลิเคชันหลายภาษาในยุโรปหรือเอเชียอาจใช้
forwardRef
เพื่อวัดขนาดขององค์ประกอบข้อความที่แปลแล้วเพื่อปรับเลย์เอาต์แบบไดนามิกสำหรับภาษาต่าง ๆ เพื่อให้แน่ใจว่าข้อความจะไม่ล้นออกจากคอนเทนเนอร์ ตัวอย่างเช่น คำในภาษาเยอรมันมักจะยาวกว่าคำในภาษาอังกฤษ ซึ่งต้องการการปรับเปลี่ยน - เลย์เอาต์จากขวาไปซ้าย (RTL): ในตะวันออกกลางและบางส่วนของเอเชีย แอปพลิเคชันมักจะต้องรองรับเลย์เอาต์ RTL
forwardRef
สามารถใช้เพื่อปรับตำแหน่งขององค์ประกอบตามทิศทางเลย์เอาต์ปัจจุบันโดยใช้โค้ด - การเข้าถึงได้ง่ายสำหรับผู้ใช้ที่หลากหลาย: ทั่วโลก การเข้าถึงได้ง่ายเป็นเรื่องที่น่ากังวลมากขึ้น นักพัฒนาอาจใช้
forwardRef
เพื่อเพิ่มการเข้าถึงสำหรับผู้ใช้ที่มีความพิการ เช่น การโฟกัสองค์ประกอบสำหรับโปรแกรมอ่านหน้าจอ (screen reader) โดยใช้โค้ด หรือการปรับลำดับแท็บของช่องในฟอร์ม - การทำงานร่วมกับ API เฉพาะภูมิภาค: นักพัฒนาที่ทำงานร่วมกับ API ในท้องถิ่น (เช่น เกตเวย์การชำระเงิน, บริการแผนที่) อาจใช้
forwardRef
เพื่อเข้าถึง DOM element ที่ API เหล่านั้นต้องการ เพื่อให้แน่ใจว่าเข้ากันได้และทำงานร่วมกันได้อย่างราบรื่น
สรุป
React.forwardRef
เป็นเครื่องมือที่ทรงพลังสำหรับการสร้างคอมโพเนนต์ React ที่นำกลับมาใช้ใหม่และประกอบกันได้ ด้วยการอนุญาตให้ parent component เข้าถึง DOM node หรือ instance ของคอมโพเนนต์ลูก forwardRef
เปิดใช้งานกรณีการใช้งานที่หลากหลาย ตั้งแต่การจัดการ DOM ไปจนถึงการทำงานร่วมกับไลบรารีของบุคคลที่สาม โดยการปฏิบัติตามแนวทางปฏิบัติที่ดีที่สุดที่ระบุไว้ในคู่มือนี้ คุณสามารถใช้ประโยชน์จาก forwardRef
ได้อย่างมีประสิทธิภาพและหลีกเลี่ยงข้อผิดพลาดทั่วไป อย่าลืมใช้ ref อย่างรอบคอบ จัดทำเอกสารสำหรับคอมโพเนนต์ของคุณอย่างชัดเจน และพิจารณาทางเลือกอื่นเมื่อเหมาะสม ด้วยความเข้าใจที่มั่นคงเกี่ยวกับ forwardRef
คุณสามารถสร้างแอปพลิเคชัน React ที่แข็งแกร่ง ยืดหยุ่น และดูแลรักษาง่ายขึ้นได้