เรียนรู้ React forwardRef: ทำความเข้าใจเกี่ยวกับการส่งต่อ Reference, การเข้าถึง DOM Nodes ของ Child Component, การสร้าง Reusable Component และการปรับปรุงความสามารถในการบำรุงรักษาโค้ด
React forwardRef: คำแนะนำที่ครอบคลุมเกี่ยวกับการส่งต่อ Reference
ใน React การเข้าถึง DOM Node ของ Child Component โดยตรงอาจเป็นสิ่งที่ท้าทาย นี่คือที่มาของ forwardRef ซึ่งเป็นกลไกในการส่งต่อ Ref ไปยัง Child Component บทความนี้จะเจาะลึกเกี่ยวกับ forwardRef อธิบายวัตถุประสงค์ การใช้งาน และประโยชน์ของมัน และช่วยให้คุณมีความรู้ในการใช้ประโยชน์จากมันอย่างมีประสิทธิภาพในโปรเจกต์ React ของคุณ
forwardRef คืออะไร
forwardRef คือ React API ที่ช่วยให้ Parent Component ได้รับ Ref ไปยัง DOM Node ภายใน Child Component หากไม่มี forwardRef Refs มักจะถูกจำกัดไว้เฉพาะ Component ที่สร้างขึ้น ข้อจำกัดนี้อาจทำให้การโต้ตอบกับ DOM พื้นฐานของ Child Component โดยตรงจาก Parent เป็นเรื่องยาก
ลองนึกภาพแบบนี้: สมมติว่าคุณมี Custom Input Component และคุณต้องการโฟกัสที่ Input Field โดยอัตโนมัติเมื่อ Component Mount หากไม่มี forwardRef Parent Component จะไม่มีทางเข้าถึง DOM Node ของ Input ได้โดยตรง เมื่อมี forwardRef Parent จะสามารถอ้างอิง Input Field และเรียกใช้เมธอด focus() บน Input Field ได้
ทำไมต้องใช้ forwardRef
ต่อไปนี้เป็นสถานการณ์ทั่วไปที่ forwardRef พิสูจน์ได้ว่ามีค่าอย่างยิ่ง:
- การเข้าถึง Child DOM Nodes: นี่คือ Use Case หลัก Parent Component สามารถจัดการหรือโต้ตอบกับ DOM Nodes ภายใน Child Component ได้โดยตรง
- การสร้าง Reusable Component: โดยการส่งต่อ Refs คุณสามารถสร้าง Component ที่มีความยืดหยุ่นและนำกลับมาใช้ใหม่ได้มากขึ้น ซึ่งสามารถรวมเข้ากับส่วนต่างๆ ของแอปพลิเคชันของคุณได้อย่างง่ายดาย
- การรวมเข้ากับ Third-Party Libraries: Third-Party Libraries บางตัวต้องการเข้าถึง DOM Nodes โดยตรง
forwardRefช่วยให้คุณรวม Libraries เหล่านี้เข้ากับ React Component ของคุณได้อย่างราบรื่น - การจัดการ Focus และ Selection: ดังที่แสดงให้เห็นก่อนหน้านี้ การจัดการ Focus และ Selection ภายใน Component Hierarchies ที่ซับซ้อนจะง่ายขึ้นมากด้วย
forwardRef
forwardRef ทำงานอย่างไร
forwardRef คือ Higher-Order Component (HOC) โดยจะใช้ Rendering Function เป็น Argument และส่งกลับ React Component Rendering Function จะได้รับ props และ ref เป็น Argument Argument ref คือ Ref ที่ Parent Component ส่งลงมา ภายใน Rendering Function คุณสามารถแนบ ref นี้กับ DOM Node ภายใน Child Component ได้
ไวยากรณ์พื้นฐาน
ไวยากรณ์พื้นฐานของ forwardRef มีดังนี้:
const MyComponent = React.forwardRef((props, ref) => {
// Component Logic ที่นี่
return <div ref={ref}>...</div>;
});
มาทำลายไวยากรณ์นี้:
React.forwardRef(): นี่คือ Function ที่ Wrapper Component ของคุณ(props, ref) => { ... }: นี่คือ Rendering Function โดยจะได้รับ Props ของ Component และ Ref ที่ส่งมาจาก Parent<div ref={ref}>...</div>: นี่คือที่ที่ Magic เกิดขึ้น คุณแนบrefที่ได้รับไปยัง DOM Node ภายใน Component ของคุณ จากนั้น Parent Component จะสามารถเข้าถึง DOM Node นี้ได้
ตัวอย่างเชิงปฏิบัติ
ลองสำรวจตัวอย่างเชิงปฏิบัติบางส่วนเพื่อแสดงให้เห็นว่า forwardRef สามารถใช้ในสถานการณ์จริงได้อย่างไร
ตัวอย่างที่ 1: การโฟกัส Input Field
ในตัวอย่างนี้ เราจะสร้าง Custom Input Component ที่จะโฟกัส Input Field โดยอัตโนมัติเมื่อ Mount
import React, { useRef, useEffect } from 'react';
const FancyInput = React.forwardRef((props, ref) => {
return (
<input ref={ref} type="text" className="fancy-input" {...props} />
);
});
function ParentComponent() {
const inputRef = useRef(null);
useEffect(() => {
if (inputRef.current) {
inputRef.current.focus();
}
}, []);
return (
<FancyInput ref={inputRef} placeholder="Focus me!" />
);
}
export default ParentComponent;
คำอธิบาย:
FancyInputสร้างขึ้นโดยใช้React.forwardRefโดยจะได้รับpropsและrefrefถูกแนบกับ Element<input>ParentComponentสร้างrefโดยใช้useRefrefถูกส่งไปยังFancyInput- ใน Hook
useEffectInput Field จะถูกโฟกัสเมื่อ Component Mount
ตัวอย่างที่ 2: Custom Button พร้อมการจัดการ Focus
มาสร้าง Custom Button Component ที่ช่วยให้ Parent ควบคุม Focus ได้
import React, { forwardRef } from 'react';
const MyButton = forwardRef((props, ref) => {
return (
<button ref={ref} className="my-button" {...props}>
{props.children}
</button>
);
});
function App() {
const buttonRef = React.useRef(null);
const focusButton = () => {
if (buttonRef.current) {
buttonRef.current.focus();
}
};
return (
<div>
<MyButton ref={buttonRef} onClick={() => alert('Button Clicked!')}>
Click Me
</MyButton>
<button onClick={focusButton}>Focus Button</button>
</div>
);
}
export default App;
คำอธิบาย:
MyButtonใช้forwardRefเพื่อส่งต่อ Ref ไปยัง Button Element- Parent Component (
App) ใช้useRefเพื่อสร้าง Ref และส่งต่อไปยังMyButton - Function
focusButtonช่วยให้ Parent สามารถโฟกัส Button โดยทางโปรแกรม
ตัวอย่างที่ 3: การรวมเข้ากับ Third-Party Library (ตัวอย่าง: react-select)
Third-Party Libraries จำนวนมากต้องการเข้าถึง DOM Node พื้นฐาน มาแสดงให้เห็นวิธีการรวม forwardRef เข้ากับสถานการณ์สมมติโดยใช้ react-select ซึ่งคุณอาจต้องเข้าถึง Input Element ของ Select
หมายเหตุ: นี่คือภาพประกอบสมมติที่เรียบง่าย โปรดดูเอกสารประกอบ react-select ที่แท้จริงสำหรับวิธีการที่รองรับอย่างเป็นทางการในการเข้าถึงและปรับแต่ง Component
import React, { useRef, useEffect } from 'react';
// สมมติว่าอินเทอร์เฟซ react-select ที่เรียบง่ายสำหรับการสาธิต
import Select from 'react-select'; // แทนที่ด้วย Import จริง
const CustomSelect = React.forwardRef((props, ref) => {
return (
<Select ref={ref} {...props} />
);
});
function MyComponent() {
const selectRef = useRef(null);
useEffect(() => {
// สมมติ: การเข้าถึง Input Element ภายใน react-select
if (selectRef.current && selectRef.current.inputRef) { // inputRef เป็น Prop สมมติ
console.log('Input Element:', selectRef.current.inputRef.current);
}
}, []);
return (
<CustomSelect
ref={selectRef}
options={[
{ value: 'chocolate', label: 'Chocolate' },
{ value: 'strawberry', label: 'Strawberry' },
{ value: 'vanilla', label: 'Vanilla' },
]}
/>
);
}
export default MyComponent;
ข้อควรพิจารณาที่สำคัญสำหรับ Third-Party Libraries:
- โปรดดูเอกสารประกอบของ Library: ตรวจสอบเอกสารประกอบของ Third-Party Library เสมอเพื่อทำความเข้าใจวิธีที่แนะนำในการเข้าถึงและจัดการ Component ภายใน การใช้วิธีการที่ไม่เป็นทางการหรือไม่ได้รับการสนับสนุนอาจนำไปสู่พฤติกรรมที่ไม่คาดฝันหรือการแตกหักในอนาคต
- การเข้าถึง: เมื่อเข้าถึง DOM Nodes โดยตรง ตรวจสอบให้แน่ใจว่าคุณรักษาสิทธิ์การเข้าถึงไว้ จัดเตรียมวิธีการอื่นสำหรับผู้ใช้ที่ต้องพึ่งพาเทคโนโลยีอำนวยความสะดวกในการโต้ตอบกับ Component ของคุณ
แนวทางปฏิบัติที่ดีที่สุดและข้อควรพิจารณา
แม้ว่า forwardRef จะเป็นเครื่องมือที่มีประสิทธิภาพ แต่สิ่งสำคัญคือต้องใช้อย่างรอบคอบ ต่อไปนี้เป็นแนวทางปฏิบัติที่ดีที่สุดและข้อควรพิจารณาที่ควรคำนึงถึง:
- หลีกเลี่ยงการใช้งานมากเกินไป: อย่าใช้
forwardRefหากมีทางเลือกที่ง่ายกว่า ลองใช้ Props หรือ Callback Function เพื่อสื่อสารระหว่าง Component การใช้forwardRefมากเกินไปอาจทำให้โค้ดของคุณเข้าใจและบำรุงรักษายากขึ้น - รักษา Encapsulation: ระวังการทำลาย Encapsulation การจัดการ DOM Nodes ของ Child Component โดยตรงอาจทำให้โค้ดของคุณเปราะบางและปรับโครงสร้างได้ยากขึ้น พยายามลดการจัดการ DOM โดยตรง และอาศัย Internal API ของ Component เมื่อใดก็ตามที่เป็นไปได้
- การเข้าถึง: เมื่อทำงานกับ Refs และ DOM Nodes ให้จัดลำดับความสำคัญของการเข้าถึงเสมอ ตรวจสอบให้แน่ใจว่า Component ของคุณสามารถใช้งานได้โดยผู้พิการ ใช้ Semantic HTML จัดเตรียม Attributes ARIA ที่เหมาะสม และทดสอบ Component ของคุณด้วยเทคโนโลยีอำนวยความสะดวก
- ทำความเข้าใจ Component Lifecycle: โปรดทราบว่า Ref จะพร้อมใช้งานเมื่อใด โดยทั่วไป Ref จะพร้อมใช้งานหลังจากที่ Component Mount แล้ว ใช้
useEffectเพื่อเข้าถึง Ref หลังจากที่ Component Render แล้ว - ใช้กับ TypeScript: หากคุณใช้ TypeScript ตรวจสอบให้แน่ใจว่าคุณพิมพ์ Refs และ Component ที่ใช้
forwardRefอย่างถูกต้อง ซึ่งจะช่วยให้คุณตรวจพบข้อผิดพลาดได้ตั้งแต่เนิ่นๆ และปรับปรุงความปลอดภัยของประเภทโดยรวมของโค้ดของคุณ
ทางเลือกอื่นสำหรับ forwardRef
ในบางกรณี มีทางเลือกอื่นในการใช้ forwardRef ที่อาจเหมาะสมกว่า:
- Props และ Callbacks: การส่งข้อมูลและพฤติกรรมลง Props มักจะเป็นวิธีที่ง่ายที่สุดและเป็นที่ต้องการมากที่สุดในการสื่อสารระหว่าง Component หากคุณต้องการส่งข้อมูลหรือทริกเกอร์ Function ใน Child เท่านั้น Props และ Callbacks มักจะเป็นตัวเลือกที่ดีที่สุด
- Context: สำหรับการแชร์ข้อมูลระหว่าง Component ที่ซ้อนกันอย่างลึกซึ้ง React's Context API สามารถเป็นทางเลือกที่ดีได้ Context ช่วยให้คุณสามารถจัดเตรียมข้อมูลให้กับ Subtree ทั้งหมดของ Component โดยไม่ต้องส่ง Props ลงไปทีละระดับด้วยตนเอง
- Imperative Handle: Hook useImperativeHandle สามารถใช้ร่วมกับ forwardRef เพื่อแสดง API ที่จำกัดและควบคุมไปยัง Parent Component แทนที่จะแสดง DOM Node ทั้งหมด ซึ่งรักษา Encapsulation ได้ดีกว่า
การใช้งานขั้นสูง: useImperativeHandle
Hook useImperativeHandle ช่วยให้คุณปรับแต่ง Instance Value ที่เปิดเผยให้กับ Parent Component เมื่อใช้ forwardRef ซึ่งให้การควบคุมมากขึ้นว่า Parent Component สามารถเข้าถึงอะไรได้บ้าง ส่งเสริม Encapsulation ที่ดีขึ้น
import React, { forwardRef, useImperativeHandle, useRef } from 'react';
const FancyInput = forwardRef((props, ref) => {
const inputRef = useRef(null);
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
},
getValue: () => {
return inputRef.current.value;
},
}));
return <input ref={inputRef} type="text" {...props} />;
});
function ParentComponent() {
const inputRef = useRef(null);
const handleFocus = () => {
inputRef.current.focus();
};
const handleGetValue = () => {
alert(inputRef.current.getValue());
};
return (
<div>
<FancyInput ref={inputRef} placeholder="Enter text" />
<button onClick={handleFocus}>Focus Input</button>
<button onClick={handleGetValue}>Get Value</button>
</div>
);
}
export default ParentComponent;
คำอธิบาย:
- Component
FancyInputใช้useRefเพื่อสร้าง Internal Ref (inputRef) สำหรับ Input Element useImperativeHandleใช้เพื่อกำหนด Custom Object ที่จะเปิดเผยให้กับ Parent Component ผ่าน Forwarded Ref ในกรณีนี้ เรากำลังแสดง Functionfocusและ FunctiongetValue- จากนั้น Parent Component สามารถเรียกใช้ Function เหล่านี้ผ่าน Ref โดยไม่ต้องเข้าถึง DOM Node ของ Input Element โดยตรง
การแก้ไขปัญหาทั่วไป
ต่อไปนี้เป็นปัญหาทั่วไปที่คุณอาจพบเมื่อใช้ forwardRef และวิธีการแก้ไขปัญหา:
- Ref เป็น Null: ตรวจสอบให้แน่ใจว่า Ref ถูกส่งลงมาอย่างถูกต้องจาก Parent Component และ Child Component แนบ Ref กับ DOM Node อย่างถูกต้อง นอกจากนี้ ตรวจสอบให้แน่ใจว่าคุณเข้าถึง Ref หลังจากที่ Component Mount แล้ว (เช่น ใน Hook
useEffect) - ไม่สามารถอ่าน Property 'focus' ของ Null ได้: โดยปกติสิ่งนี้บ่งชี้ว่า Ref ไม่ได้แนบกับ DOM Node อย่างถูกต้อง หรือ DOM Node ยังไม่ได้ Render ตรวจสอบโครงสร้าง Component ของคุณอีกครั้ง และตรวจสอบให้แน่ใจว่า Ref ถูกแนบกับ Element ที่ถูกต้อง
- ข้อผิดพลาดประเภทใน TypeScript: ตรวจสอบให้แน่ใจว่า Refs ของคุณถูกพิมพ์อย่างถูกต้อง ใช้
React.RefObject<HTMLInputElement>(หรือประเภท HTML Element ที่เหมาะสม) เพื่อกำหนดประเภทของ Ref ของคุณ นอกจากนี้ ตรวจสอบให้แน่ใจว่า Component ที่ใช้forwardRefถูกพิมพ์อย่างถูกต้องด้วยReact.forwardRef<HTMLInputElement, Props> - พฤติกรรมที่ไม่คาดฝัน: หากคุณกำลังประสบปัญหาพฤติกรรมที่ไม่คาดฝัน ให้ตรวจสอบโค้ดของคุณอย่างละเอียด และตรวจสอบให้แน่ใจว่าคุณไม่ได้จัดการ DOM โดยไม่ได้ตั้งใจในลักษณะที่อาจรบกวนกระบวนการ Render ของ React ใช้ React DevTools เพื่อตรวจสอบ Component Tree ของคุณและระบุปัญหาที่อาจเกิดขึ้น
บทสรุป
forwardRef เป็นเครื่องมือที่มีค่าในคลังแสงของนักพัฒนา React ช่วยให้คุณเชื่อมช่องว่างระหว่าง Parent และ Child Component ช่วยให้สามารถจัดการ DOM โดยตรงและปรับปรุงความสามารถในการนำ Component กลับมาใช้ใหม่ได้ ด้วยการทำความเข้าใจวัตถุประสงค์ การใช้งาน และแนวทางปฏิบัติที่ดีที่สุด คุณสามารถใช้ประโยชน์จาก forwardRef เพื่อสร้างแอปพลิเคชัน React ที่มีประสิทธิภาพ ยืดหยุ่น และบำรุงรักษาได้มากขึ้น อย่าลืมใช้อย่างรอบคอบ จัดลำดับความสำคัญของการเข้าถึง และพยายามรักษา Encapsulation ไว้เสมอเมื่อเป็นไปได้
คำแนะนำที่ครอบคลุมนี้ได้ให้ความรู้และตัวอย่างแก่คุณเพื่อนำ forwardRef ไปใช้ในโปรเจกต์ React ของคุณอย่างมั่นใจ ขอให้สนุกกับการเขียนโค้ด!