Side Effects คืออะไร? ทำไมโปรแกรมเมอร์ต้องรู้จัก
ในโลกของการเขียนโปรแกรมด้วย React ซึ่งเป็นเครื่องมือสร้างหน้าจอเว็บไซต์ยอดนิยม การเขียนโค้ดส่วนใหญ่จะเป็นการแสดงผลข้อมูลจากตัวแปร แต่ในความเป็นจริง เว็บไซต์ต้องมีการทำงานที่ 'อยู่นอกเหนือ' จากการแสดงผลปกติ เช่น การดึงข้อมูลจาก Server (เครื่องคอมพิวเตอร์ที่เก็บข้อมูลหลัก) หรือการสั่งให้เบราว์เซอร์เปลี่ยนชื่อแถบเว็บไซต์
เราเรียกการทำงานเหล่านี้ว่า Side Effects (ผลข้างเคียง) เปรียบเสมือนการที่คุณสั่งอาหารในร้านแล้วนอกจากจะได้อาหาร (คือการแสดงผล) คุณยังได้ใบเสร็จหรือการบริการเสริมเพิ่มขึ้นมาด้วย การจัดการสิ่งเหล่านี้ใน React จำเป็นต้องใช้ฟังก์ชันพิเศษที่เรียกว่า useEffect เพื่อให้มั่นใจว่าโค้ดเหล่านี้จะทำงานถูกจังหวะและไม่ทำให้โปรแกรมพัง
หากคุณไม่เข้าใจการทำงานของ useEffect เว็บไซต์ของคุณอาจจะเกิดอาการค้าง หรือดึงข้อมูลซ้ำๆ จน Server ล่มได้ การเรียนรู้เรื่องนี้จึงเป็นพื้นฐานสำคัญที่ช่วยให้คุณก้าวข้ามจากมือใหม่ไปสู่การสร้างแอปพลิเคชันที่เสถียรและทำงานได้อย่างมืออาชีพครับ
ทำความรู้จักกับ Lifecycle ของ Component
Lifecycle (วงจรชีวิต) ของ Component (ส่วนประกอบเล็กๆ ในหน้าจอ) คือช่วงเวลาตั้งแต่ที่มันถูกสร้างขึ้นมาจนกระทั่งถูกลบออกไปจากหน้าจอ เหมือนกับคนเราที่เริ่มจากเกิด เติบโต และจากไป ใน React 19 เราต้องรู้จังหวะเหล่านี้เพื่อที่จะได้สั่งให้โค้ดทำงานได้ถูกเวลาที่สุด
จังหวะสำคัญที่คุณต้องจำมี 3 ช่วง คือช่วง Mounting (ตอนเกิด - เมื่อ Component ปรากฏบนหน้าจอครั้งแรก), Updating (ตอนเปลี่ยน - เมื่อข้อมูลภายในเปลี่ยนแล้วหน้าจอต้องวาดใหม่), และ Unmounting (ตอนจากไป - เมื่อ Component ถูกลบออกจากหน้าจอ) ซึ่ง useEffect ถูกออกแบบมาให้รับมือกับจังหวะเหล่านี้ได้อย่างแม่นยำ
ลองจินตนาการว่าคุณเป็นพนักงานต้อนรับที่โรงแรม จังหวะที่คุณต้องกล่าวต้อนรับลูกค้าคือตอนที่เขาเดินเข้ามา (Mount) จังหวะที่คุณต้องถามข้อมูลเพิ่มคือตอนที่เขาเปลี่ยนใจขอห้องใหม่ (Update) และจังหวะที่คุณต้องโบกมือลาคือตอนที่เขาเช็คเอาท์ (Unmount) การจัดการข้อมูลใน React ก็ทำงานในลักษณะเดียวกันนี้เป๊ะครับ
การทำงานของ useEffect และ Dependency Array
useEffect คือ Hook (ฟังก์ชันพิเศษของ React) ที่เอาไว้จัดการ Side Effects โดยเฉพาะ มันรับค่า 2 อย่างคือ ฟังก์ชันที่ต้องการให้ทำงาน และ Dependency Array (อาร์เรย์ที่บอกว่าเมื่อไหร่ควรทำงาน) ตัวอาร์เรย์นี้เองที่เป็นกุญแจสำคัญในการควบคุมว่าโค้ดจะรันตอนไหน
หากเราใส่ค่าไว้ใน Array (เช่น [count]) React จะคอยตรวจสอบว่าค่า count มีการเปลี่ยนแปลงหรือไม่ ถ้าเปลี่ยนค่อยสั่งให้โค้ดใน useEffect ทำงานใหม่ แต่ถ้าไม่ใส่ค่าอะไรเลย หรือใส่ Array ว่างๆ ไว้ มันจะมีพฤติกรรมที่แตกต่างกันอย่างสิ้นเชิง ซึ่งเป็นจุดที่มือใหม่มักจะสับสนมากที่สุด
การเข้าใจกลไกนี้จะช่วยให้คุณควบคุมประสิทธิภาพของแอปได้ หากคุณลืมใส่เงื่อนไขให้ถูกต้อง โปรแกรมอาจจะทำงานซ้ำซ้อนโดยไม่จำเป็น ทำให้เว็บไซต์ช้าลงอย่างเห็นได้ชัด ดังนั้นการฝึกเขียนและสังเกตผลลัพธ์ผ่าน console.log จึงเป็นวิธีเรียนรู้ที่ดีที่สุดครับ
import { useEffect, useState } from 'react';
function ExampleComponent() {
const [count, setCount] = useState(0);
// ตัวอย่างการใช้ useEffect
useEffect(() => {
console.log("ทำงานทุกครั้งที่ count เปลี่ยน!");
}, [count]); // นี่คือ Dependency Array
return <button onClick={() => setCount(count + 1)}>เพิ่มค่า</button>;
}
ในตัวอย่างนี้ โค้ดใน useEffect จะทำงานก็ต่อเมื่อค่า count เปลี่ยนแปลงเท่านั้น หากคุณกดปุ่ม setCount ระบบจะเช็ค Array นี้ก่อน ถ้าค่าเดิมกับค่าใหม่ไม่เหมือนกัน ถึงจะยอมให้โค้ดข้างในทำงานครับ
กรณีศึกษา: [], [dep], และ No Deps
เรามาดูความต่างของ 3 รูปแบบหลักที่โปรแกรมเมอร์ต้องเจอ เริ่มจาก No Deps (ไม่ใส่ Array เลย) คือการสั่งให้ทำงาน ทุกครั้ง ที่ Component มีการอัปเดต ต่อมาคือ Empty Array [] ซึ่งจะสั่งให้ทำงาน แค่ครั้งเดียว ตอนเริ่มโปรแกรมเท่านั้น และสุดท้ายคือ Dependency Array [dep] ที่จะทำงานเมื่อค่าตัวแปรนั้นเปลี่ยนเท่านั้น
จุดที่มือใหม่พลาดบ่อยที่สุดคือการลืมใส่ Array หรือใส่ผิดเงื่อนไข ทำให้เกิด Infinite Loop (การทำงานวนซ้ำไม่รู้จบ) เช่น การสั่งให้ดึงข้อมูลจาก API แล้วดันไปอัปเดตค่าใน useEffect โดยไม่มีเงื่อนไขหยุด ทำให้โปรแกรมพยายามดึงข้อมูลใหม่ซ้ำๆ จนเบราว์เซอร์ค้างไปเลย
เปรียบเทียบง่ายๆ คือ No Deps เหมือนคุณสั่งให้เพื่อนจดทุกอย่างที่คนในห้องพูด, Empty Array คือคุณสั่งให้เพื่อนจดแค่ตอนเช็คชื่อเข้าห้องครั้งเดียว, ส่วน Dependency Array คือคุณสั่งให้เพื่อนจดแค่ตอนที่ครูเดินเข้าห้องเท่านั้น การเลือกว่าจะใช้แบบไหนขึ้นอยู่กับว่าคุณต้องการให้โค้ดทำงานบ่อยแค่ไหนครับ
// 1. ไม่มี Array (ทำงานทุกครั้งที่ Render)
useEffect(() => { console.log("รันทุกครั้ง!"); });
// 2. Array ว่าง (รันครั้งเดียวตอนโหลดเสร็จ)
useEffect(() => { console.log("รันแค่ตอนเกิด!"); }, []);
// 3. มี Dependency (รันตอนค่าเปลี่ยน)
useEffect(() => { console.log("รันเมื่อ user เปลี่ยน!"); }, [user]);
การใช้ [] เหมาะสำหรับการดึงข้อมูลจาก Server ครั้งแรก ส่วนการใช้ [user] เหมาะสำหรับการอัปเดตข้อมูลเมื่อมีการเลือกผู้ใช้คนใหม่ครับ
ข้อควรระวังสำหรับมือใหม่
กฎเหล็ก ของ useEffect คือห้ามใส่ค่าใน Dependency Array ที่คุณไม่ได้ใช้ในฟังก์ชัน และห้ามทำให้เกิดการอัปเดตค่าที่ส่งผลให้ useEffect ทำงานวนไปไม่จบสิ้น โปรแกรมเมอร์มืออาชีพมักใช้เครื่องมือที่เรียกว่า ESLint (โปรแกรมช่วยตรวจจับความผิดพลาดในโค้ด) มาช่วยเตือนเรื่องนี้โดยเฉพาะ
อีกเรื่องที่สำคัญคือการทำ Cleanup Function (ฟังก์ชันทำความสะอาด) บางครั้งเราเปิดการเชื่อมต่อไว้ เช่น การฟังเสียงหรือการตั้งเวลา (Timer) ถ้าเราไม่ปิดมันตอนที่ Component ถูกลบออกไป (Unmount) มันจะกลายเป็น Memory Leak (การกินทรัพยากรเครื่องโดยเปล่าประโยชน์) ทำให้โปรแกรมทำงานช้าลงเมื่อเปิดใช้นานๆ
จำไว้ว่า useEffect ไม่ใช่ที่สำหรับใส่โค้ดทุกอย่างที่คุณนึกออก มันมีไว้สำหรับ "ผลข้างเคียง" เท่านั้น หากคุณสามารถคำนวณค่าได้โดยตรงในฟังก์ชัน ก็ไม่จำเป็นต้องใช้ Hook นี้ครับ การเขียนโค้ดที่เรียบง่ายและตรงไปตรงมาคือทักษะที่สำคัญที่สุดของนักพัฒนา
สรุป: การนำไปใช้ในโปรเจกต์จริง
สรุปสั้นๆ คือ useEffect คือเครื่องมือจัดการเหตุการณ์ภายนอก ให้คุณมองว่ามันคือ "ตัวจัดการจังหวะ" ของแอปพลิเคชัน ไม่ว่าจะเป็นการดึงข้อมูลจาก API, การบันทึกค่าลง localStorage (พื้นที่เก็บข้อมูลในเบราว์เซอร์), หรือการจัดการเหตุการณ์ต่างๆ ของหน้าจอ
ตัวอย่างการนำไปใช้จริง: สมมติคุณทำแอปแสดงรายชื่อสินค้า คุณต้องใช้ useEffect แบบ [] เพื่อดึงข้อมูลสินค้าจาก API ทันทีที่หน้าเว็บโหลดเสร็จ และถ้าคุณมีช่องค้นหา คุณต้องใช้ useEffect แบบ [searchQuery] เพื่อให้แอปดึงข้อมูลใหม่ทุกครั้งที่ผู้ใช้พิมพ์คำค้นหา เปลี่ยนไปตามสิ่งที่ผู้ใช้ทำครับ
จงฝึกฝนโดยการลองสร้างโปรเจกต์เล็กๆ อย่างแอป Todo List หรือแอปพยากรณ์อากาศ แล้วลองสังเกตการทำงานผ่าน console.log ในจังหวะต่างๆ จะช่วยให้คุณเห็นภาพชัดเจนขึ้นว่า useEffect ทำงานอย่างไร และเมื่อคุณเริ่มชินแล้ว การจัดการกับ Side Effects จะกลายเป็นเรื่องง่ายที่อยู่ในสัญชาตญาณของคุณเองครับ