useCallback — แนวคิดหลัก ฮุกการจดจำฟังก์ชันใน React

ผู้แต่ง: IT Sectr เผยแพร่เมื่อ: 2026-07-04 เวลาอ่าน: 9 นาที

useCallback คือฮุก React ที่ส่งคืนเวอร์ชันที่ถูกจดจำของฟังก์ชันซึ่งจะไม่เปลี่ยนแปลงระหว่างการเรนเดอร์จนกว่าค่าที่ขึ้นต่อ (dependencies) จะเปลี่ยนแปลง ต่างจากการประกาศฟังก์ชันปกติภายในคอมโพเนนต์ (ซึ่งสร้างฟังก์ชันใหม่ทุกครั้งที่เรนเดอร์) useCallback จะทำให้การอ้างอิงฟังก์ชันมีเสถียรภาพ ป้องกันการเรนเดอร์ซ้ำที่ไม่จำเป็นของคอมโพเนนต์ลูกที่ถูกปรับแต่งด้วย React.memo ตาม React Documentation (2025) useCallback จะมีประโยชน์ก็ต่อเมื่อใช้กับ React.memo หรือฮุกที่ขึ้นอยู่กับการอ้างอิงที่เสถียร

ประเด็นสำคัญ

  • useCallback — ฮุกสำหรับจดจำฟังก์ชัน รักษาการอ้างอิงระหว่างการเรนเดอร์
  • React.memo — useCallback จะมีประสิทธิภาพเมื่อใช้กับ React.memo เพื่อป้องกันการเรนเดอร์ซ้ำ
  • ค่าที่ขึ้นต่อ — ฟังก์ชันจะถูกสร้างใหม่เมื่อค่าที่ระบุในอาร์เรย์เปลี่ยนแปลงเท่านั้น
  • การอ้างอิงที่เสถียร — useCallback รับประกันว่าการอ้างอิงฟังก์ชันจะไม่เปลี่ยนแปลงโดยไม่จำเป็น
  • อย่าใช้มากเกินไป — การใช้ useCallback มากเกินไปโดยไม่มี React.memo ทำให้ประสิทธิภาพลดลง

useCallback ใน React คืออะไร

useCallback คือฮุกที่ถูกเพิ่มใน React 16.8 ซึ่งจดจำฟังก์ชัน: ส่งคืนการอ้างอิงเดียวกันจนกว่าค่าที่ขึ้นต่อจะเปลี่ยนแปลง หากไม่มี useCallback ทุกการประกาศฟังก์ชันภายในคอมโพเนนต์จะสร้างออบเจกต์ฟังก์ชันใหม่ทุกครั้งที่เรนเดอร์ สำหรับค่าพื้นฐานแล้วสิ่งนี้ไม่สังเกตเห็น แต่เมื่อส่ง callback เหล่านี้ไปยังคอมโพเนนต์ลูกที่ถูกปรับแต่งด้วย React.memo ทุกการอ้างอิงใหม่จะทำให้เกิดการเรนเดอร์ซ้ำของคอมโพเนนต์ลูก

ในเชิงวากยสัมพันธ์ useCallback เทียบเท่ากับ useMemo สำหรับฟังก์ชัน: useCallback(fn, deps) เป็นตัวย่อของ useMemo(() => fn, deps) React จะเก็บฟังก์ชันที่ถูกจดจำไว้ในพื้นที่จัดเก็บภายในของโหนด fiber และเปรียบเทียบค่าที่ขึ้นต่อทุกครั้งที่เรนเดอร์ หากค่าที่ขึ้นต่อไม่เปลี่ยนแปลง (Object.is สำหรับแต่ละรายการ) ฟังก์ชันก่อนหน้าจะถูกส่งคืน

ตาม React Documentation — useCallback (2025) คุณไม่ควรครอบทุกฟังก์ชันด้วย useCallback ฮุกมีต้นทุนของมัน: การเรียกฮุก การเปรียบเทียบค่าที่ขึ้นต่อ และการจัดสรรหน่วยความจำสำหรับอาร์เรย์ของค่าที่ขึ้นต่อ หากคอมโพเนนต์เรียบง่ายและไม่มีโครงสร้างต้นไม้ลึกกับ React.memo useCallback จะทำให้แอปพลิเคชันช้าลงเท่านั้น การปรับแต่งควร วัดผลได้ ไม่ใช่โดยสัญชาตญาณ

jsx
import { useCallback } from 'react';

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

    // การอ้างอิงที่เสถียร — ฟังก์ชันเดียวกันจนกว่า deps จะเปลี่ยน
    const handleClick = useCallback(() => {
        setCount(prev => prev + 1);
    }, []);

    return <Child onClick={handleClick} />;
}

การจดจำฟังก์ชันทำงานอย่างไร

การจดจำใน useCallback ขึ้นอยู่กับการแคชผลลัพธ์ของการเรียกฟังก์ชัน React จะเก็บ closure ที่ถูกสร้างขึ้นในขณะที่เรนเดอร์ครั้งแรกและส่งคืนในการเรนเดอร์ครั้งต่อๆ ไปตราบใดที่ค่าที่ขึ้นต่อยังคงไม่เปลี่ยนแปลง ภายในโหนด fiber ทุกการเรียก useCallback จะสร้างโหนดในรายการเชื่อมโยงของฮุก ซึ่งเก็บค่าที่ขึ้นต่อก่อนหน้าและค่าที่ถูกจดจำ

การเปรียบเทียบค่าที่ขึ้นต่อจะดำเนินการอย่างเคร่งครัดผ่าน Object.is — การเปรียบเทียบระดับพื้นผิวโดยไม่ตรวจสอบออบเจกต์หรืออาร์เรย์แบบลึก หากค่าที่ขึ้นต่อเป็นออบเจกต์หรืออาร์เรย์ การอ้างอิงใหม่ทุกครั้งที่เรนเดอร์จะถือว่าเป็นการเปลี่ยนแปลง ดังนั้น อาร์เรย์ของค่าที่ขึ้นต่อควรประกอบด้วยค่าพื้นฐานหรือการอ้างอิงที่เสถียร (เช่น จาก useRef หรือ useMemo)

ตาม React Core Team — Optimization Guide (2024) ต้นทุนของการจดจำประกอบด้วยสามส่วน: การจัดสรรอาร์เรย์ของค่าที่ขึ้นต่อในทุกการเรนเดอร์ การวนซ้ำและเปรียบเทียบองค์ประกอบผ่าน Object.is และค่าใช้จ่ายในการเก็บขยะที่อาจเกิดขึ้นเมื่อสร้างใหม่ สำหรับคอมโพเนนต์ที่มี useCallback หลายร้อยตัว สิ่งนี้อาจเห็นได้ชัด — ดังนั้น การเลือกใช้ ฮุกอย่างมีวิจารณญาณจึงสำคัญ

สถานการณ์ไม่มี useCallbackมี useCallback
การสร้างฟังก์ชันใหม่ทุกครั้งที่เรนเดอร์เหมือนเดิมกับ deps ที่เสถียร
ส่งต่อไปยัง React.memoลูกเรนเดอร์ซ้ำลูกไม่เรนเดอร์ซ้ำ
ในอาร์เรย์ของ useEffectเอฟเฟกต์เริ่มต้นใหม่เอฟเฟกต์เสถียร
ค่าใช้จ่ายน้อยที่สุดการเปรียบเทียบค่าที่ขึ้นต่อ + หน่วยความจำ

useCallback และประสิทธิภาพ

มีความเชื่อผิดๆ อย่างแพร่หลายว่า useCallback ช่วยปรับปรุงประสิทธิภาพโดยอัตโนมัติ ในความเป็นจริง ตามลำพัง (ไม่มี React.memo) useCallback กลับทำให้แอปพลิเคชันช้าลงเล็กน้อยเนื่องจากต้นทุนการเปรียบเทียบค่าที่ขึ้นต่อ ฮุกจะให้ประโยชน์จริงๆ ในสามสถานการณ์เท่านั้น: ป้องกันการเรนเดอร์ซ้ำของคอมโพเนนต์ React.memo, ทำให้ callback ใน useEffect เสถียร และส่ง callback ไปยังฮุกแบบกำหนดเองที่ขึ้นอยู่กับความเท่ากันของการอ้างอิง

กฎง่ายๆ คือ: จนกว่าคุณจะตรวจพบปัญหาด้านประสิทธิภาพผ่าน React DevTools Profiler — อย่าใช้ useCallback ทีม React ย้ำหลายครั้งว่าการปรับแต่งก่อนเวลาอันควรเป็นต้นเหตุของปัญหาทั้งปวง ขั้นแรกเขียนโค้ดที่สะอาดโดยไม่มีการจดจำ วัดผล ค้นหาจุดคอขวดในโปรไฟเลอร์ แล้วค่อยเพิ่ม useCallback ในที่ที่ จำเป็นจริงๆ

jsx
// การปรับแต่งที่วัดผลได้: Child ถูกครอบด้วย React.memo
const Child = React.memo(({ onClick }) => {
    console.log('Child เรนเดอร์ซ้ำแล้ว');
    return <button onClick={onClick}>Click</button>;
});

function Parent() {
    const handleClick = useCallback(() => {
        console.log('ถูกคลิก');
    }, []);
    return <Child onClick={handleClick} />;
}

ตาม Dan Abramov — Before You memo() (2024) กว่า 90% ของการใช้ useCallback ในโปรเจกต์โอเพนซอร์สนั้นซ้ำซ้อน นักพัฒนาครอบทุกฟังก์ชัน "เผื่อไว้" โดยไม่วัดผล ทางเลือก: หากคอมโพเนนต์ลูกหนักและการเรนเดอร์ซ้ำมีต้นทุนสูง — React.memo + useCallback ก็สมเหตุสมผล หากคอมโพเนนต์ลูกเบา — การเรนเดอร์ซ้ำถูกกว่าการเปรียบเทียบค่าที่ขึ้นต่อ

เมื่อใดควรใช้ useCallback

สถานการณ์แรกคือ React.memo หากคอมโพเนนต์ลูกถูกครอบด้วย React.memo และรับฟังก์ชัน callback เป็น prop หากไม่มี useCallback คอมโพเนนต์ลูกจะเรนเดอร์ซ้ำทุกครั้งที่พ่อแม่เรนเดอร์ แม้ว่าข้อมูลของมันจะไม่เปลี่ยนแปลง useCallback ทำให้การอ้างอิงเสถียร และ React.memo สามารถข้ามการเรนเดอร์ซ้ำได้อย่างถูกต้อง

สถานการณ์ที่สองคือ useEffect ที่มี callback ในค่าที่ขึ้นต่อ หากฟังก์ชันถูกส่งไปยังอาร์เรย์ของค่าที่ขึ้นต่อของ useEffect ทุกการอ้างอิงใหม่จะเริ่มต้นเอฟเฟกต์ใหม่ useCallback รับประกันว่าการอ้างอิงเสถียร และเอฟเฟกต์จะทำงานเมื่อข้อมูลจริงเปลี่ยนแปลงเท่านั้น ไม่ใช่ทุกครั้งที่เรนเดอร์ สิ่งนี้สำคัญโดยเฉพาะสำหรับการสมัครรับข้อมูลและคำขอ

  • ลูก React.memo — ป้องกันการเรนเดอร์ซ้ำของคอมโพเนนต์ลูกที่ถูกจดจำเมื่อส่ง callback
  • ค่าที่ขึ้นต่อของ useEffect — ทำให้ฟังก์ชันในอาร์เรย์ของค่าที่ขึ้นต่อของเอฟเฟกต์เสถียร ป้องกันการเริ่มต้นใหม่ที่ไม่จำเป็น
  • ฮุกแบบกำหนดเอง — หากฮุกรับ callback และขึ้นอยู่กับความเท่ากันของการอ้างอิง useCallback เป็นสิ่งจำเป็น
  • ค่าคอนเท็กซ์ — หากฟังก์ชันถูกส่งในค่าคอนเท็กซ์ useCallback จะทำให้การอ้างอิงเสถียร
jsx
// useCallback สำหรับค่าที่ขึ้นต่อของ useEffect ที่เสถียร
const fetchData = useCallback(async (id) => {
    const res = await fetch(`/api/${id}`);
    setData(res.data);
}, []); // การอ้างอิงที่เสถียร ไม่ถูกสร้างใหม่เลย

useEffect(() => {
    fetchData(props.id);
}, [props.id, fetchData]); // เอฟเฟกต์ทำงานเมื่อ props.id เปลี่ยนแปลงเท่านั้น

useCallback กับ useMemo

ความแตกต่างหลักระหว่าง useCallback และ useMemo คือสิ่งที่แต่ละตัวจดจำ useCallback จดจำฟังก์ชัน: useCallback(fn, deps) ส่งคืน fn (เวอร์ชันเดียวกันหรือก่อนหน้า) useMemo จดจำผลลัพธ์ของการเรียกฟังก์ชัน: useMemo(() => computeExpensive(a, b), [a, b]) ส่งคืนค่าที่คำนวณแล้ว ไม่ใช่ฟังก์ชัน

ในทางเทคนิค useCallback คือนํ้าตาลเชิงวากยสัมพันธ์เหนือ useMemo: useCallback(fn, deps) เทียบเท่ากับ useMemo(() => fn, deps) วากยสัมพันธ์นี้มีไว้เพื่อความสามารถในการอ่านเท่านั้น — เพื่อให้นักพัฒนาเห็นชัดเจนว่ากำลังจดจำฟังก์ชัน ไม่ใช่ค่า ไม่มีความแตกต่างด้านประสิทธิภาพระหว่าง useCallback และ useMemo กับฟังก์ชัน — ทั้งคู่สร้างโค้ดที่เหมือนกัน

ฮุกจดจำวากยสัมพันธ์การใช้งาน
useCallbackฟังก์ชัน (การอ้างอิง)useCallback(fn, deps)Callback สำหรับคอมโพเนนต์ลูก
useMemoผลลัพธ์การคำนวณuseMemo(() => value, deps)การคำนวณที่มีต้นทุนสูง การจดจำออบเจกต์
jsx
// สิ่งเหล่านี้เทียบเท่ากัน:
const handleClick = useCallback(() => doSomething(a, b), [a, b]);
const handleClick = useMemo(() => () => doSomething(a, b), [a, b]);

ข้อผิดพลาดทั่วไปกับ useCallback

ข้อผิดพลาดที่พบบ่อยที่สุดคือ การครอบอย่างไม่มีประโยชน์ ทุกฟังก์ชันใน useCallback โดยไม่มี React.memo บนคอมโพเนนต์ลูก หากคอมโพเนนต์ลูกไม่ได้ถูกครอบด้วย React.memo มันก็ยังคงเรนเดอร์ซ้ำทุกครั้งที่พ่อแม่เรนเดอร์ ไม่ว่าการอ้างอิง callback จะเปลี่ยนหรือไม่ก็ตาม useCallback โดยไม่มี React.memo คือต้นทุนที่ไม่มีประโยชน์

  • useCallback โดยไม่มี React.memo — คอมโพเนนต์ลูกยังคงเรนเดอร์ซ้ำ; การปรับแต่งไม่มีประโยชน์
  • ค่าที่ขึ้นต่อที่ถูกลืม — หากตัวแปรที่ใช้ภายใน callback ไม่ได้อยู่ใน deps callback จะมี stale closure
  • การจดจำมากเกินไป — ครอบทุกฟังก์ชัน รวมถึง onClick ธรรมดากับ console.log
  • ออบเจกต์และอาร์เรย์ในค่าที่ขึ้นต่อ — การอ้างอิงใหม่ทุกครั้งที่เรนเดอร์ถือว่าเป็นการเปลี่ยนแปลง การจดจำไม่มีประโยชน์
jsx
// ❌ ไร้ประโยชน์: ไม่มี React.memo บนลูก
const handle = useCallback(() => doStuff(), []);
<Child onClick={handle} />; // Child ยังคงเรนเดอร์ซ้ำโดยไม่มี React.memo

// ❌ Stale closure: ค่าที่ขึ้นต่อหายไป
const handle = useCallback(() => {
    console.log(count); // count เป็น 0 เสมอ — stale closure!
}, []);

// ✅ ถูกต้อง: รวมค่าที่ขึ้นต่อ
const handle = useCallback(() => {
    console.log(count);
}, [count]);

ปัญหา stale closure ใน useCallback สามารถแก้ไขได้โดยรวมตัวแปรที่ใช้ทั้งหมดไว้ในอาร์เรย์ของค่าที่ขึ้นต่อ eslint-plugin-react-hooks พร้อม exhaustive-deps จะตรวจสอบโดยอัตโนมัติว่าตัวแปรทั้งหมดจากเนื้อหาของ callback อยู่ในอาร์เรย์ หาก callback ใช้ setState ซึ่งไม่เปลี่ยนแปลงระหว่างการเรนเดอร์ ก็สามารถรวมไว้ใน deps ได้อย่างปลอดภัย — React รับประกันความเสถียรของ setState

คำถามที่พบบ่อย

จำเป็นต้องครอบทุกฟังก์ชันด้วย useCallback หรือไม่?

ไม่ useCallback มีความหมายในสามกรณีเท่านั้น: คอมโพเนนต์ลูกถูกครอบด้วย React.memo ฟังก์ชันถูกใช้ในอาร์เรย์ของค่าที่ขึ้นต่อของ useEffect หรือฟังก์ชันถูกส่งไปยังฮุกแบบกำหนดเองที่ขึ้นอยู่กับความเท่ากันของการอ้างอิง ในกรณีอื่น useCallback เพิ่มค่าใช้จ่ายโดยไม่มีประโยชน์ ทีม React แนะนำให้เขียนโดยไม่มีการปรับแต่งก่อน และเพิ่มตามผลลัพธ์ของการทำโปรไฟล์

อะไรเร็วกว่า — useCallback หรือฟังก์ชันใหม่ทุกครั้งที่เรนเดอร์?

สำหรับคอมโพเนนต์ธรรมดา — ฟังก์ชันใหม่ทุกครั้งที่เรนเดอร์เร็วกว่าเล็กน้อย เนื่องจาก useCallback ใช้ทรัพยากรในการเปรียบเทียบค่าที่ขึ้นต่อและจัดสรรอาร์เรย์ สำหรับคอมโพเนนต์ที่มีโครงสร้างต้นไม้ React.memo ลึก useCallback ชนะโดยป้องกันการเรนเดอร์ซ้ำขององค์ประกอบลูกนับพัน วัดและเปรียบเทียบแทนการเดา — ใช้ React DevTools Profiler เพื่อการประเมินตามวัตถุประสงค์

สามารถใช้ useCallback กับฟังก์ชัน async ได้หรือไม่?

ได้ useCallback ทำงานกับฟังก์ชัน async เช่นเดียวกับฟังก์ชันซิงโครนัส ฮุกจดจำตัวฟังก์ชันเอง และผลลัพธ์ (Promise) จะถูกส่งคืนทุกครั้งที่เรียกใช้ ฟังก์ชัน async ภายใน useCallback เป็นรูปแบบทั่วไปสำหรับ callback โหลดข้อมูลที่เสถียรซึ่งใช้ใน useEffect: const fetchData = useCallback(async (id) => {...}, [])

จะแก้ไขปัญหากับ useCallback และ React.memo ได้อย่างไร?

ใช้ React DevTools Profiler — มันแสดงว่าคอมโพเนนต์ใดเรนเดอร์ซ้ำและเพราะอะไร สำหรับการตรวจสอบเชิงโปรแกรม ให้เพิ่ม console.log หรือใช้ useWhyDidYouUpdate — ไลบรารีที่บันทึกสาเหตุของการเรนเดอร์ซ้ำ สาเหตุหลัก: prop เปลี่ยน (รวมถึงการอ้างอิง callback) สถานะเปลี่ยน หรือคอนเท็กซ์เปลี่ยน หาก useCallback ไม่ช่วย — ตรวจสอบว่าค่าที่ขึ้นต่อทั้งหมดถูกระบุอย่างถูกต้อง

จะส่ง useCallback ไปยังคอมโพเนนต์ลูกโดยไม่มี React.memo ได้อย่างไร?

แม้ไม่มี React.memo useCallback ก็มีประโยชน์เมื่อใช้กับ useMemo สำหรับค่าคอนเท็กซ์ หากคุณส่งออบเจกต์ที่มีฟังก์ชันไปยัง Context.Provider ให้ครอบการสร้างออบเจกต์ใน useMemo และแต่ละฟังก์ชันใน useCallback ซึ่งจะป้องกันการเรนเดอร์ซ้ำของผู้ใช้คอนเท็กซ์ทั้งหมดเมื่อฟังก์ชันใดฟังก์ชันหนึ่งเปลี่ยนแปลง แต่สำหรับการส่ง callback โดยตรงใน props โดยไม่มี React.memo จะไม่มีประโยชน์จาก useCallback

สรุป

  • useCallback — ฮุกสำหรับจดจำฟังก์ชัน ส่งคืนการอ้างอิงที่เสถียรจนกว่าค่าที่ขึ้นต่อจะเปลี่ยนแปลง
  • การทำงานร่วมกับ React.memo — useCallback มีประสิทธิภาพเฉพาะกับ React.memo บนคอมโพเนนต์ลูก
  • การเปรียบเทียบค่าที่ขึ้นต่อ — ผ่าน Object.is; ออบเจกต์/อาร์เรย์ใน deps ทำลายการจดจำ
  • วัดผล อย่าเดา — เพิ่ม useCallback หลังจากตรวจพบจุดคอขวดในโปรไฟเลอร์เท่านั้น
  • Closure ที่เสถียร — รวมตัวแปรที่ใช้ทั้งหมดในอาร์เรย์ของค่าที่ขึ้นต่อ มิฉะนั้น — stale closure
  • useCallback กับ useMemo — useCallback จดจำฟังก์ชัน useMemo จดจำผลลัพธ์การคำนวณ
  • การใช้มากเกินไป — กว่า 90% ของการใช้ useCallback ในโปรเจกต์จริงคือการปรับแต่งก่อนเวลาอันควร

เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร

IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ

ปรึกษาโครงการ

อ่านเพิ่มเติม