ทำความรู้จัก useRef และเหตุผลที่ต้องระบุ Type
เวลาเราเขียนโปรแกรมด้วย React บางครั้งเราจำเป็นต้องเข้าถึง DOM (ส่วนประกอบของหน้าเว็บ เช่น ช่องกรอกข้อความหรือปุ่มกด) โดยตรง เพื่อสั่งให้มันทำงานบางอย่าง เช่น สั่งให้ช่องกรอกข้อความขยับไปโฟกัส หรือสั่งให้วิดีโอเล่นเอง
เครื่องมือที่ใช้ทำเรื่องนี้คือ useRef (ตัวช่วยเก็บค่าที่คงอยู่ตลอดการทำงานของคอมโพเนนต์) ซึ่งเปรียบเสมือน "กล่องเก็บของ" ที่เราเอาไว้หยิบของชิ้นเดิมมาใช้ใหม่ได้โดยที่หน้าเว็บไม่จำเป็นต้องวาดใหม่
เมื่อเราใช้ TypeScript (ภาษาที่เพิ่มกฎเกณฑ์ความปลอดภัยให้ JavaScript) เราต้องบอกกล่องนี้ให้ชัดเจนว่าข้างในจะเก็บอะไร ถ้าไม่บอก TypeScript จะเดาไม่ออกว่าเราจะเอาไปทำอะไรต่อ ซึ่งอาจทำให้โปรแกรมพังได้ง่ายๆ ครับ
การเข้าถึง HTML Element ด้วย useRef
ถ้าเราต้องการให้ useRef เข้าถึง HTML Element (แท็กพื้นฐานของเว็บ เช่น input, div) เราต้องระบุประเภทของมันให้ชัดเจน เพื่อให้ TypeScript ช่วยเตือนเวลาเราเขียนคำสั่งที่ผิดพลาด
ลองจินตนาการว่าเรามีช่องกรอกข้อความ แล้วอยากให้มันถูกเลือกทันทีที่หน้าเว็บโหลดเสร็จ เราต้องใช้ useRef(null) เพื่อบอกว่ากล่องนี้จะเก็บช่องกรอกข้อมูลนะ และเริ่มแรกให้มันเป็นค่าว่างไปก่อน
จุดที่มือใหม่มักพลาดคือการลืมใส่ null เป็นค่าเริ่มต้น ทำให้ TypeScript ฟ้องว่าค่าอาจจะเป็น undefined (ค่าที่ยังไม่ได้กำหนด) ซึ่งจะทำให้เราสั่งงานช่องกรอกข้อมูลไม่ได้ครับ
import { useRef } from 'react';
function MyInput() {
// ระบุว่าเป็น HTMLInputElement และเริ่มต้นด้วย null
const inputRef = useRef<HTMLInputElement>(null);
const focusInput = () => {
// ใช้เครื่องหมาย ? เพื่อป้องกันกรณีที่ element ยังไม่อยู่ในหน้าเว็บ
inputRef.current?.focus();
};
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={focusInput}>กดเพื่อโฟกัส</button>
</div>
);
}
จากโค้ดด้านบน เราใช้ inputRef.current เพื่อหยิบตัวช่องกรอกข้อมูลออกมาใช้งาน การใส่เครื่องหมาย ? (Optional Chaining) คือการบอกว่าถ้ากล่องนี้ยังว่างอยู่ ก็ไม่ต้องทำอะไร ไม่ต้องพยายามสั่งงานให้โปรแกรมเด้งปิดตัวลง
การเก็บค่าทั่วไปด้วย Mutable Values Ref
นอกจากเข้าถึงหน้าเว็บแล้ว useRef ยังใช้เก็บค่าที่เปลี่ยนแปลงได้โดยไม่ทำให้หน้าเว็บวาดใหม่ (Re-render) เราเรียกว่า Mutable Values (ค่าที่แก้ไขได้) เช่น การเก็บค่าตัวเลขเพื่อคำนวณ หรือเก็บค่า Timer ID (รหัสอ้างอิงเวลา) เพื่อเอาไว้หยุดการทำงาน
การระบุ Type (ชนิดข้อมูล) ในกรณีนี้ทำได้ง่ายมาก แค่ใส่ชนิดข้อมูลลงไปในวงเล็บมุม useRef(0) เพื่อบอกว่ากล่องนี้จะเก็บตัวเลขนะ และเริ่มต้นที่เลขศูนย์
การใช้ useRef แบบนี้เหมาะมากสำหรับข้อมูลที่ต้องจำไว้ในเบื้องหลัง แต่ไม่ได้ส่งผลโดยตรงต่อสิ่งที่ผู้ใช้งานมองเห็นบนหน้าจอครับ
import { useRef } from 'react';
function Counter() {
// เก็บค่าตัวเลขที่เปลี่ยนแปลงได้โดยไม่ทำให้หน้าเว็บวาดใหม่
const countRef = useRef<number>(0);
const increment = () => {
countRef.current += 1;
console.log("ค่าปัจจุบันคือ:", countRef.current);
};
return <button onClick={increment}>เพิ่มค่าในกล่อง</button>;
}
สังเกตว่าเราไม่ต้องใส่ null ในกรณีนี้เพราะเรากำหนดค่าเริ่มต้นให้เป็นตัวเลข 0 ไปเลย TypeScript จะเข้าใจทันทีว่า countRef.current ต้องเป็นตัวเลขเสมอ ทำให้เรานำไปบวกลบหรือคำนวณต่อได้อย่างปลอดภัยครับ
ข้อควรระวังและวิธีจัดการกับค่าว่าง
ปัญหาคลาสสิกของมือใหม่คือการพยายามเข้าถึง .current ทั้งที่มันยังเป็น null อยู่ โดยเฉพาะตอนที่พยายามสั่งงานทันทีที่เปิดหน้าเว็บ TypeScript จะคอยดักเราไว้เสมอ ซึ่งเป็นเรื่องที่ดีมากครับ
วิธีแก้ที่นิยมคือการใช้ Optional Chaining (เครื่องหมาย ?.) ตามที่เห็นในตัวอย่างแรก หรือถ้าเรามั่นใจ 100% ว่ามันมีตัวตนแน่นอน เราสามารถใช้เครื่องหมาย ! (Non-null Assertion) เพื่อบอก TypeScript ว่า "เชื่อฉันเถอะ ตรงนี้มีค่าแน่นอน ไม่ต้องเตือน"
จำไว้ว่าการใช้ ! เป็นเหมือนดาบสองคม ถ้าเรามั่นใจผิดขึ้นมา โปรแกรมจะพังทันที ดังนั้นใช้ ?. จะปลอดภัยกว่าสำหรับการเริ่มต้นเรียนรู้ครับ ความปลอดภัยของโค้ดสำคัญกว่าความสั้นเสมอ
สรุป: เลือกใช้ให้ถูกงาน
สรุปคือ useRef มีสองหน้าที่หลัก คือเข้าถึง DOM และเก็บค่าไว้เบื้องหลัง การเลือกใช้ Type ให้ถูกต้องคือหัวใจสำคัญที่ทำให้เราเขียนโค้ดได้แบบมืออาชีพและลดบั๊ก (ข้อผิดพลาดของโปรแกรม) ลงได้มหาศาล
ถ้าคุณกำลังทำโปรเจกต์ เช่น "ระบบจับเวลาการทำข้อสอบ" คุณอาจใช้ useRef(null) เพื่อเก็บรหัสของตัวจับเวลา ซึ่งอาจจะมีค่าเป็นตัวเลขหรือไม่มีเลยก็ได้ในตอนแรก
ฝึกระบุ Type ให้เป็นนิสัยตั้งแต่วันนี้ครับ เมื่อคุณคุ้นเคยกับมันแล้ว การทำงานร่วมกับทีมหรือการดูแลโปรเจกต์ขนาดใหญ่ในอนาคตจะเป็นเรื่องที่ง่ายขึ้นมาก เพราะ TypeScript จะกลายเป็นเพื่อนคู่คิดที่คอยเตือนคุณทุกฝีก้าวครับ