useEffect คือฮุก React ที่ช่วยให้คุณดำเนินการเอฟเฟกต์ข้างเคียงในคอมโพเนนต์แบบฟังก์ชัน โดยแทนที่เมธอดวงจรชีวิตของคอมโพเนนต์แบบคลาส: componentDidMount, componentDidUpdate และ componentWillUnmount ตาม React Documentation (2025) useEffect จะทำงานหลังจากที่ React คอมมิตการเปลี่ยนแปลงไปยัง DOM ซึ่งรับประกันการเข้าถึงโครงสร้าง DOM จริง ฮุกนี้รับฟังก์ชันเอฟเฟกต์และอาร์เรย์ดีเพนเดนซีที่เลือกได้ซึ่งควบคุมความถี่ในการทำงาน
ประเด็นสำคัญ
useEffect คือฮุกที่ถูกเพิ่มใน React 16.8 สำหรับดำเนินการเอฟเฟกต์ข้างเคียงในคอมโพเนนต์แบบฟังก์ชัน เอฟเฟกต์ข้างเคียงคือการดำเนินการที่ไม่เกี่ยวข้องโดยตรงกับการเรนเดอร์ UI: คำขอ HTTP ไปยัง API, การสมัครรับอีเวนต์, การทำงานกับตัวจับเวลา, การจัดการ DOM, การบันทึกและการรวมกับไลบรารีของบุคคลที่สาม
ก่อนมีฮุก การดำเนินการทั้งหมดเหล่านี้ต้องวางในเมธอดวงจรชีวิตของคอมโพเนนต์แบบคลาส: componentDidMount สำหรับการเริ่มต้น, componentDidUpdate สำหรับการตอบสนองต่อการเปลี่ยนแปลงของ props, componentWillUnmount สำหรับการทำความสะอาด useEffect รวมสามสถานการณ์เป็น API เดียว โดยอาร์เรย์ดีเพนเดนซีเป็นตัวกำหนดว่าเอฟเฟกต์ควรทำงานเมื่อใด สิ่งนี้ทำให้ตรรกะ ง่ายขึ้น และลดการซ้ำซ้อนของโค้ด โดยเฉพาะในสถานการณ์ที่มีการสมัครรับ
ตาม React DevTools Usage Survey (2024) useEffect เป็นฮุกที่ได้รับความนิยมเป็นอันดับสองรองจาก useState ซึ่งใช้ใน 89% ของแอปพลิเคชัน React นักพัฒนาส่วนใหญ่ใช้มันสำหรับการดึงข้อมูล การซิงโครไนซ์กับระบบภายนอก และการจัดการการสมัครรับอีเวนต์ DOM
import { useEffect } from 'react';
function UserProfile({ userId }) {
useEffect(() => {
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(data => setUser(data));
}, [userId]);
}
useEffect ทำงานฟังก์ชันเอฟเฟกต์ที่ส่งผ่านหลังจากที่ React เรนเดอร์เสร็จและอัปเดต DOM นี่คือความแตกต่างสำคัญจากการคำนวณระหว่างเรนเดอร์: เอฟเฟกต์ไม่ได้บล็อกการเรนเดอร์ ซึ่งสำคัญอย่างยิ่งต่อ ประสิทธิภาพ UX หากเอฟเฟกต์ทำงานแบบซิงโครนัส ผู้ใช้จะเห็นอินเทอร์เฟซค้างระหว่างการโหลดข้อมูล
วงจรชีวิตของเอฟเฟกต์ทั่วไปประกอบด้วยสามเฟส เมื่อเมานต์คอมโพเนนต์ React จะดำเนินการเอฟเฟกต์ ในการอัปเดตแต่ละครั้ง หากดีเพนเดนซีอย่างน้อยหนึ่งตัวในอาร์เรย์เปลี่ยนแปลง React จะดำเนินการฟังก์ชันทำความสะอาดของเอฟเฟกต์ก่อนหน้า จากนั้นจึงดำเนินการเอฟเฟกต์ใหม่ เมื่ออันเมานต์คอมโพเนนต์ จะดำเนินการเฉพาะฟังก์ชันทำความสะอาดเท่านั้น
ตาม React Team — useEffect RFC (2024) การนำไปใช้ภายในของ useEffect ใช้คิวเอฟเฟกต์ข้างเคียงในโครงสร้าง fiber หลังจากคอมมิตการเปลี่ยนแปลง (เฟสคอมมิต) React จะเดินผ่านคิวนี้และเรียกฟังก์ชันเอฟเฟกต์ตามลำดับที่ประกาศในคอมโพเนนต์ แต่ละโหนด fiber จะเก็บข้อมูลอ้างอิงถึงเอฟเฟกต์ก่อนหน้าเพื่อการทำความสะอาดและการทำงานอีกครั้งที่ถูกต้อง
| ขั้นตอน | การกระทำของ React | เมื่อใดทำงาน |
|---|---|---|
| การเมานต์ | เรียกฟังก์ชันเอฟเฟกต์ | หลังจากการเรนเดอร์ครั้งแรก |
| การอัปเดต | ทำความสะอาด → เอฟเฟกต์ | เมื่อดีเพนเดนซีเปลี่ยนแปลง |
| การอันเมานต์ | ทำความสะอาดเท่านั้น | เมื่อคอมโพเนนต์ถูกลบ |
อาร์เรย์ดีเพนเดนซี — อาร์กิวเมนต์ที่สองของ useEffect — กำหนดว่าเอฟเฟกต์ควรทำงานอีกครั้งเมื่อใด React เปรียบเทียบค่าแต่ละค่าในอาร์เรย์กับการเรนเดอร์ก่อนหน้าโดยใช้ Object.is หากค่าใดค่าหนึ่งเปลี่ยนแปลงไป เอฟเฟกต์จะทำงานอีกครั้ง หากอาร์เรย์ว่าง ([]) เอฟเฟกต์จะทำงานเพียงครั้งเดียวหลังจากการเมานต์
การเลือกดีเพนเดนซีที่ถูกต้องเป็นส่วนที่ยากที่สุดในการทำงานกับ useEffect อาร์เรย์ต้องประกอบด้วยตัวแปรและฟังก์ชันทั้งหมดที่ใช้ภายในเอฟเฟกต์ซึ่งสามารถเปลี่ยนแปลงได้ระหว่างการเรนเดอร์ การละเว้นดีเพนเดนซีนำไปสู่ stale closure — เอฟเฟกต์เห็นค่าที่ล้าสมัยจากการเรนเดอร์ก่อนหน้า การรวมดีเพนเดนซีที่ไม่จำเป็นทำให้เกิด การทำงานซ้ำมากเกินไป และข้อบกพร่องที่อาจเกิดขึ้น
// ดีเพนเดนซีควบคุมเมื่อแอฟเฟกต์ทำงานซ้ำ
useEffect(() => {
document.title = `User: ${user.name}`;
}, [user.name]); // ทำงଲนซ้ำเมื่อ user.name เปลี่ยนแปลงเท่านั้น
// eslint-disable-next-line react-hooks/exhaustive-deps
// หากคุณละเว้นดีเพนเดนซี คุณจะได้รับข้อมูลที่ล้าสมัย
React มี eslint-plugin-react-hooks พร้อมกฎ exhaustive-deps ซึ่งตรวจสอบความสมบูรณ์ของอาร์เรย์ดีเพนเดนซีโดยอัตโนมัติ ตาม Meta Engineering Blog (2024) การเปิดใช้ปลั๊กอินนี้ช่วยลดข้อบกพร่องที่เกี่ยวข้องกับฮุกได้ 72% แนะนำให้แก้ไขคำเตือน exhaustive-deps ทั้งหมดแทนที่จะระงับด้วยการคอมเมนต์ ยกเว้นในกรณีหายากที่มีตรรกะที่กำหนดเอง
หากคุณไม่ส่งอาร์เรย์ดีเพนเดนซีเลย useEffect จะทำงานหลังจากการเรนเดอร์ทุกครั้ง ซึ่งอาจมีประโยชน์สำหรับการซิงโครไนซ์ DOM หรือการบันทึก แต่ส่วนใหญ่แล้วมักเป็นข้อผิดพลาด: เอฟเฟกต์ทำงานบ่อยเกินไป นำไปสู่การสูญเสียประสิทธิภาพ ในกรณีส่วนใหญ่ คุณควรส่งอาร์เรย์ว่าง (หนึ่งครั้งเมื่อเมานต์) หรืออาร์เรย์ที่มี props/state เฉพาะ
อาร์เรย์ว่าง ([]) หมายความว่าเอฟเฟกต์ไม่ได้ขึ้นอยู่กับค่าใดๆ และทำงานอย่างเคร่งครัดเพียงครั้งเดียว ซึ่งเทียบเท่ากับ componentDidMount ในคอมโพเนนต์แบบคลาส อย่างไรก็ตาม ต้องจำไว้ว่า: หากเอฟเฟกต์ใช้ props หรือ state ที่ไม่ได้อยู่ในอาร์เรย์ดีเพนเดนซี เอฟเฟกต์จะใช้ค่าเริ่มต้นและจะไม่เห็นการอัปเดต สิ่งนี้เรียกว่า การจับภาพที่ล้าสมัย (stale capture) และมักเป็นที่มาของข้อบกพร่องที่ค้นหายาก
| อาร์เรย์ดีเพนเดนซี | พฤติกรรม | เทียบเท่าในคลาส |
|---|---|---|
| ไม่มีอาร์กิวเมนต์ | หลังการเรนเดอร์ทุกครั้ง | componentDidUpdate |
| [] | หนึ่งครั้งเมื่อเมานต์ | componentDidMount |
| [a, b] | เมื่อ a หรือ b เปลี่ยนแปลง | คล้าย ComponentWillReceiveProps |
| return cleanup | จัดการการอันเมานต์ | componentWillUnmount |
ฟังก์ชันทำความสะอาดคือฟังก์ชันที่ useEffect สามารถส่งกลับจากคอลแบ็กของมัน React จะเรียกมันเมื่ออันเมานต์คอมโพเนนต์และก่อนที่จะทำงานเอฟเฟกต์อีกครั้งเมื่อดีเพนเดนซีเปลี่ยนแปลง การทำความสะอาดจำเป็นสำหรับการยกเลิกการสมัครรับ ตัวจับเวลา คำขอ และทรัพยากรใดๆ ที่ต้องถูกปล่อย
ตัวอย่างทั่วไปคือการสมัครรับ WebSocket เมื่อเมานต์ จะสร้างการเชื่อมต่อ; เมื่ออัปเดตดีเพนเดนซี จะสร้างใหม่ (ทำความสะอาดปิดอันเก่า, เอฟเฟกต์เปิดอันใหม่); เมื่ออันเมานต์ จะถูกปิด หากไม่มีการทำความสะอาด ทุกครั้งที่เมานต์คอมโพเนนต์ใหม่จะสร้างการเชื่อมต่อ WebSocket ใหม่ ซึ่งนำไปสู่ การรั่วไหลของหน่วยความจำ และการเชื่อมต่อหลายรายการ
useEffect(() => {
const socket = new WebSocket('wss://api.example.com');
socket.onmessage = event => setData(event.data);
// ฟังก์ชันทำความสะอาด — ทำงานเมื่ออันเมานต์และก่อนทำงานซ้ำ
return () => {
socket.close();
};
}, []);
ตาม React Documentation (2025) AbortController เป็นแนวทางสมัยใหม่สำหรับการยกเลิกคำขอ fetch ในการทำความสะอาด หากเอฟเฟกต์ทำคำขอ HTTP และคอมโพเนนต์ถูกอันเมานต์ก่อนที่จะเสร็จสมบูรณ์ คำขอจะยังคงทำงานต่อไป และ setState หลังอันเมานต์จะทำให้เกิดข้อผิดพลาด สร้าง AbortController ภายในเอฟเฟกต์และเรียก controller.abort() ในการทำความสะอาดเพื่อยกเลิกคำขอ
ข้อผิดพลาดที่พบบ่อยที่สุดคือ การละเว้นดีเพนเดนซี ตัวอย่างเช่น เอฟเฟกต์ใช้ prop userId แต่อาร์เรย์ดีเพนเดนซีว่าง ผลลัพธ์คือ เอฟเฟกต์ทำงานหนึ่งครั้งด้วยค่า userId เริ่มต้นและไม่เคยตอบสนองต่อการเปลี่ยนแปลงของมัน นักพัฒนาเห็นว่าคอมโพเนนต์ได้รับ userId ใหม่ แต่ข้อมูลไม่อัปเดต eslint-plugin-react-hooks พร้อมกฎ exhaustive-deps ตรวจจับข้อบกพร่องดังกล่าวโดยอัตโนมัติ
// ❌ Race condition — ไม่มีการยกเลิก
useEffect(() => {
fetch(`/api/user/${userId}`).then(res => setUser(res));
}, [userId]);
// ✅ แก้ไขด้วย AbortController
useEffect(() => {
const controller = new AbortController();
fetch(`/api/user/${userId}`, { signal: controller.signal })
.then(res => setUser(res));
return () => controller.abort();
}, [userId]);
ในการแก้ไขปัญหาลูปไม่สิ้นสุด หลีกเลี่ยงการวางตรรกะที่อัปเดต state โดยอ้างอิงจาก state ก่อนหน้าใน useEffect ใช้รูปแบบฟังก์ชันของ setState หรือย้ายการคำนวณออกนอกเอฟเฟกต์ หากเอฟเฟกต์สมัครรับอีเวนต์พื้นที่จัดเก็บหรือเบราว์เซอร์ ตรวจสอบให้แน่ใจว่าอินสแตนซ์ของตัวฟังถูกสร้างขึ้นครั้งเดียว ไม่ใช่ทุกการเรนเดอร์
คำถามที่พบบ่อย
โดยตรง — ไม่ได้ เพราะ useEffect คาดหวังให้ส่งคืนฟังก์ชันแบบซิงโครนัสหรือ undefined หากคอลแบ็กถูกประกาศเป็น async ฟังก์ชันจะส่งคืน Promise ซึ่ง React ไม่สนใจ และกลไกการทำความสะอาดจะหยุดทำงาน วิธีแก้ไข: เรียกฟังก์ชัน async ภายในเอฟเฟกต์: useEffect(() => { async function load() { ... }; load(); }, [])
ไม่มีข้อจำกัด React แนะนำให้แยกตรรกะที่ไม่เกี่ยวข้องกันเป็น useEffect แต่ละอัน แม้ว่าจะมีอาร์เรย์ดีเพนเดนซีเดียวกันก็ตาม แต่ละเอฟเฟกต์ควรรับผิดชอบงานข้างเคียงที่กำหนดไว้อย่างชัดเจน: หนึ่งอันสำหรับการสมัครรับ, อีกอันสำหรับการโหลดข้อมูล, ที่สามสำหรับการซิงโครไนซ์ชื่อแท็บ สิ่งนี้ทำให้เข้าใจและดีบักได้ง่ายขึ้น
ใน React Strict Mode (เฉพาะโหมดพัฒนา) เอฟเฟกต์ทั้งหมดจะถูกเมานต์ อันเมานต์ และเมานต์อีกครั้ง นี่คือคุณลักษณะ ไม่ใช่ข้อบกพร่อง — React ตรวจสอบว่าการทำความสะอาดทำงานถูกต้องหรือไม่ หากหลังจากอันเมานต์และเมานต์ใหม่แล้วเอฟเฟกต์ทำงานไม่ถูกต้อง (เช่น การสมัครรับซ้ำ) แสดงว่าการทำความสะอาดของคุณไม่สมบูรณ์ ในโหมดโปรดักชัน เอฟเฟกต์จะทำงานหนึ่งครั้ง
ใช้ AbortController สร้าง controller ภายในเอฟเฟกต์ ส่ง controller.signal ไปยัง fetch และเรียก controller.abort() ในการทำความสะอาด หากคอมโพเนนต์ถูกอันเมานต์ก่อนคำขอเสร็จสมบูรณ์ fetch จะถูกยกเลิกและ setState จะไม่ถูกเรียก สิ่งนี้ป้องกันเงื่อนไขการแข่งขันและข้อผิดพลาด “Can't perform a React state update on an unmounted component”
useEffect จะทำงานหลังจากการเรนเดอร์ทุกครั้งโดยไม่มีข้อยกเว้น ซึ่งหมายความว่า setState ใดๆ ภายในเอฟเฟกต์จะทำให้เกิดการเรนเดอร์ใหม่ → เอฟเฟกต์ใหม่ → ลูปไม่สิ้นสุด ในทางปฏิบัติ เอฟเฟกต์ที่ไม่มีอาร์เรย์ดีเพนเดนซีมักจะเป็นข้อผิดพลาดเสมอ ข้อยกเว้นคือการบันทึกหรือการซิงโครไนซ์กับระบบภายนอกที่ทุกการเรนเดอร์ต้องการการซิงโครไนซ์
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม