useeffect react คือ — คู่มือฉบับสมบูรณ์ 2026 | SiamCafe Blog

useeffect react คือ — คู่มือฉบับสมบูรณ์ 2026 | SiamCafe Blog

แนะนำ useEffect ใน React คืออะไร? ทำไมถึงสำคัญ?

ในโลกของการพัฒนาเว็บแอปพลิเคชันด้วย React, useEffect คือหนึ่งใน Hooks ที่ทรงพลังและถูกใช้งานมากที่สุด รองจาก useState มันเป็นเครื่องมือที่ช่วยให้นักพัฒนาจัดการกับ side effects หรือผลกระทบข้างเคียงที่เกิดขึ้นใน component ได้อย่างมีประสิทธิภาพ

แต่ก่อนที่เราจะลงลึกไปถึงรายละเอียด เรามาทำความเข้าใจกันก่อนว่า “side effects” ในบริบทของ React หมายถึงอะไร? โดยทั่วไปแล้ว React component ควรจะเป็น pure function ที่รับ props และ state แล้ว return JSX ออกมา แต่ในโลกของความเป็นจริง เราจำเป็นต้องทำงานหลายอย่างที่ไม่ใช่แค่การ render UI เช่น:

  • การดึงข้อมูลจาก API (Data Fetching)
  • การจัดการกับ DOM โดยตรง
  • การตั้งค่า subscriptions หรือ event listeners
  • การทำงานกับ timers เช่น setTimeout, setInterval
  • การบันทึกข้อมูลลง localStorage
  • การจัดการกับการเชื่อมต่อ WebSocket

นี่คือจุดที่ useEffect เข้ามามีบทบาท มันช่วยให้เราสามารถแยก logic ที่เป็น side effects ออกจาก rendering logic ได้อย่างชัดเจน ซึ่งเป็นหัวใจสำคัญของ React functional component ยุคใหม่

ในบทความนี้ เราจะเจาะลึกทุกแง่มุมของ useEffect ตั้งแต่พื้นฐานไปจนถึงเทคนิคขั้นสูง พร้อมตัวอย่างการใช้งานจริง และ best practices ที่ควรรู้ในปี 2026

พื้นฐานของ useEffect — ทำงานอย่างไร?

useEffect เป็น Hook ที่ถูกเรียกหลังจาก component ถูก render เสร็จแล้ว (after render) โดยมันจะทำงานในลักษณะ asynchronous ซึ่งหมายความว่า มันจะไม่บล็อกการแสดงผลของ UI

โครงสร้างพื้นฐานของ useEffect

import React, { useEffect } from 'react';

function MyComponent() {
  useEffect(() => {
    // โค้ด side effects ของคุณที่นี่
    console.log('Component rendered or updated!');
    
    // Optional: cleanup function
    return () => {
      console.log('Cleanup before next effect or unmount');
    };
  }, [dependencies]); // dependency array
}

useEffect รับพารามิเตอร์ 2 ตัว:

  1. Effect function — ฟังก์ชันที่ทำงานหลังจาก render
  2. Dependency array — อาร์เรย์ของค่าที่ effect ต้องพึ่งพา (optional)

การทำงานของ Dependency Array

Dependency array เป็นหัวใจสำคัญที่ควบคุมว่า effect จะทำงานเมื่อไหร่:

รูปแบบ Dependency Array พฤติกรรมการทำงาน
useEffect(fn) — ไม่มี array ทำงานทุกครั้งที่ component re-render
useEffect(fn, []) — array ว่าง ทำงานเพียงครั้งเดียวหลังจาก mount (componentDidMount)
useEffect(fn, [dep1, dep2]) — มี dependencies ทำงานเมื่อค่าที่ระบุเปลี่ยนแปลง

ตัวอย่างการทำงานของ useEffect แบบต่างๆ

import React, { useState, useEffect } from 'react';

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  // 1. ทำงานทุกครั้งที่ re-render (ไม่แนะนำให้ใช้บ่อย)
  useEffect(() => {
    console.log('Component rendered');
  });

  // 2. ทำงานครั้งเดียวตอน mount (componentDidMount)
  useEffect(() => {
    console.log('Component mounted');
  }, []);

  // 3. ทำงานเมื่อ userId เปลี่ยนแปลง
  useEffect(() => {
    async function fetchUser() {
      setLoading(true);
      try {
        const response = await fetch(`/api/users/${userId}`);
        const data = await response.json();
        setUser(data);
      } catch (error) {
        console.error('Failed to fetch user:', error);
      } finally {
        setLoading(false);
      }
    }
    
    fetchUser();
  }, [userId]); // effect นี้จะทำงานใหม่เมื่อ userId เปลี่ยน

  if (loading) return <div>Loading...</div>;
  return <div>{user?.name}</div>;
}

การจัดการกับ Side Effects ทั่วไปด้วย useEffect

ในส่วนนี้ เราจะดูตัวอย่างการใช้งานจริงของ useEffect สำหรับ side effects ที่พบบ่อยที่สุดในการพัฒนาแอปพลิเคชัน React

1. การดึงข้อมูลจาก API (Data Fetching)

การ fetch ข้อมูลเป็นหนึ่งใน use case ที่พบบ่อยที่สุด เราควรจัดการกับ loading state, error state, และ cleanup อย่างเหมาะสม

import React, { useState, useEffect } from 'react';

function ProductList() {
  const [products, setProducts] = useState([]);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    let isCancelled = false; // ป้องกัน memory leak

    async function loadProducts() {
      try {
        setIsLoading(true);
        setError(null);
        
        const response = await fetch('https://api.example.com/products');
        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`);
        }
        const data = await response.json();
        
        if (!isCancelled) {
          setProducts(data);
        }
      } catch (err) {
        if (!isCancelled) {
          setError(err.message);
        }
      } finally {
        if (!isCancelled) {
          setIsLoading(false);
        }
      }
    }

    loadProducts();

    // Cleanup function
    return () => {
      isCancelled = true;
    };
  }, []); // ดึงข้อมูลครั้งเดียวตอน mount

  if (isLoading) return <div>กำลังโหลดสินค้า...</div>;
  if (error) return <div>เกิดข้อผิดพลาด: {error}</div>;

  return (
    <div>
      <h2>รายการสินค้า</h2>
      <ul>
        {products.map(product => (
          <li key={product.id}>{product.name} - {product.price} บาท</li>
        ))}
      </ul>
    </div>
  );
}

2. การจัดการ Event Listeners

การเพิ่ม event listeners ต้องมี cleanup เสมอเพื่อป้องกัน memory leak

import React, { useState, useEffect } from 'react';

function WindowResizeTracker() {
  const [windowSize, setWindowSize] = useState({
    width: window.innerWidth,
    height: window.innerHeight
  });

  useEffect(() => {
    function handleResize() {
      setWindowSize({
        width: window.innerWidth,
        height: window.innerHeight
      });
    }

    // เพิ่ม event listener
    window.addEventListener('resize', handleResize);

    // Cleanup: ลบ event listener เมื่อ component unmount
    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []); // effect นี้ทำงานครั้งเดียว

  return (
    <div>
      <p>ความกว้างหน้าต่าง: {windowSize.width}px</p>
      <p>ความสูงหน้าต่าง: {windowSize.height}px</p>
    </div>
  );
}

3. การทำงานกับ Timers

การใช้ setTimeout หรือ setInterval จำเป็นต้อง cleanup เสมอ

import React, { useState, useEffect } from 'react';

function CountdownTimer({ initialSeconds = 60 }) {
  const [seconds, setSeconds] = useState(initialSeconds);
  const [isRunning, setIsRunning] = useState(false);

  useEffect(() => {
    if (!isRunning || seconds <= 0) return;

    const intervalId = setInterval(() => {
      setSeconds(prev => {
        if (prev <= 1) {
          clearInterval(intervalId);
          setIsRunning(false);
          return 0;
        }
        return prev - 1;
      });
    }, 1000);

    // Cleanup: clear interval เมื่อ component unmount หรือ dependencies เปลี่ยน
    return () => {
      clearInterval(intervalId);
    };
  }, [isRunning, seconds]);

  return (
    <div>
      <p>เวลาเหลือ: {seconds} วินาที</p>
      <button onClick={() => setIsRunning(true)} disabled={isRunning}>
        เริ่มนับถอยหลัง
      </button>
      <button onClick={() => {
        setSeconds(initialSeconds);
        setIsRunning(false);
      }}>
        รีเซ็ต
      </button>
    </div>
  );
}

useEffect vs useLayoutEffect — ควรใช้อันไหน?

นอกจาก useEffect แล้ว React ยังมี Hook ที่คล้ายกันอีกตัวคือ useLayoutEffect ซึ่งมีความแตกต่างที่สำคัญ:

คุณสมบัติ useEffect useLayoutEffect
เวลาทำงาน หลังจาก DOM ถูกอัปเดตและ paint เสร็จ (asynchronous) หลังจาก DOM ถูกอัปเดต แต่ก่อนที่ browser จะ paint (synchronous)
บล็อกการแสดงผล? ไม่บล็อก — user อาจเห็น flicker บล็อกการ paint — เหมาะสำหรับงานที่ต้องการวัด DOM
การใช้งาน ส่วนใหญ่: data fetching, subscriptions, timers วัดขนาด/ตำแหน่ง DOM, animation ที่ต้องการ sync
Performance ดีกว่า — ไม่บล็อก UI อาจทำให้ UI รู้สึกช้า ถ้าใช้ไม่เหมาะสม

เมื่อไหร่ควรใช้ useLayoutEffect?

  • เมื่อคุณต้องการวัดขนาดหรือตำแหน่งของ DOM element (เช่น getBoundingClientRect)
  • เมื่อคุณต้องการปรับแต่ง DOM ก่อนที่ user จะเห็น (ป้องกัน flicker)
  • เมื่อทำงานกับ animation ที่ต้องการความแม่นยำในการ sync

ตัวอย่างการใช้งาน useLayoutEffect

import React, { useLayoutEffect, useRef, useState } from 'react';

function Tooltip({ text, targetRect }) {
  const tooltipRef = useRef(null);
  const [position, setPosition] = useState({ top: 0, left: 0 });

  useLayoutEffect(() => {
    if (!tooltipRef.current || !targetRect) return;

    const tooltipRect = tooltipRef.current.getBoundingClientRect();
    const viewportWidth = window.innerWidth;
    
    // คำนวณตำแหน่งให้ tooltip อยู่ในหน้าจอเสมอ
    let left = targetRect.left + targetRect.width / 2 - tooltipRect.width / 2;
    if (left < 0) left = 0;
    if (left + tooltipRect.width > viewportWidth) {
      left = viewportWidth - tooltipRect.width;
    }

    setPosition({
      top: targetRect.top - tooltipRect.height - 8,
      left: left
    });
  }, [text, targetRect]); // คำนวณใหม่เมื่อ text หรือตำแหน่ง target เปลี่ยน

  if (!targetRect) return null;

  return (
    <div
      ref={tooltipRef}
      style={{
        position: 'fixed',
        top: position.top,
        left: position.left,
        backgroundColor: 'black',
        color: 'white',
        padding: '8px',
        borderRadius: '4px',
        zIndex: 1000
      }}
    >
      {text}
    </div>
  );
}

Best Practices และ Anti-Patterns ที่ควรรู้

การใช้ useEffect อย่างไม่ถูกต้องอาจนำไปสู่ปัญหาหนัก เช่น infinite loops, memory leaks, หรือ performance ที่แย่ลง มาดูแนวทางปฏิบัติที่ดีที่สุดกัน

Do’s — สิ่งที่ควรทำ

  1. แยก Effect ตามความรับผิดชอบ — อย่ารวมทุกอย่างไว้ใน effect เดียว
  2. ใส่ทุกค่าที่ effect ใช้ลงใน dependency array — ยกเว้นฟังก์ชัน setState
  3. ใช้ cleanup function เสมอ — สำหรับ subscriptions, timers, event listeners
  4. ใช้ custom hooks — เพื่อแยก logic ที่ซับซ้อน
  5. จัดการ loading/error state — ให้ user รู้สถานะของแอป

Don’ts — ข้อควรระวัง

  • อย่าเรียกใช้ setState โดยไม่มี dependencies — จะเกิด infinite loop
  • อย่าใช้ useEffect สำหรับ logic ที่ควรอยู่ใน event handler — เช่น การ submit form
  • อย่าใช้ object/array literal เป็น dependency — เพราะมันสร้าง reference ใหม่ทุกครั้ง
  • อย่าลืม cleanup — โดยเฉพาะใน development mode ที่ strict mode ทำงาน 2 รอบ

ตัวอย่าง Anti-Pattern ที่พบบ่อย

// ❌ Anti-pattern: Infinite loop
useEffect(() => {
  setCount(count + 1); // count เปลี่ยน -> re-render -> effect ทำงานอีกครั้ง
}, [count]);

// ❌ Anti-pattern: Object literal as dependency
useEffect(() => {
  fetchUser(options); // options = { id: userId }
}, [{ id: userId }]); // Object ใหม่ทุกครั้ง -> effect ทำงานทุก render

// ❌ Anti-pattern: Missing dependency
useEffect(() => {
  const timer = setInterval(() => {
    console.log(count); // count ไม่ได้อยู่ใน dependency
  }, 1000);
  return () => clearInterval(timer);
}, []); // count จะเป็น stale เสมอ

// ✅ วิธีที่ถูกต้อง
useEffect(() => {
  const timer = setInterval(() => {
    console.log(count);
  }, 1000);
  return () => clearInterval(timer);
}, [count]); // ใส่ count ใน dependency

// หรือใช้ functional update
useEffect(() => {
  const timer = setInterval(() => {
    setCount(prev => prev + 1); // ไม่ต้องใช้ count
  }, 1000);
  return () => clearInterval(timer);
}, []); // ปลอดภัยแล้ว

การจัดการ Dependencies อย่างมืออาชีพ

หนึ่งในความท้าทายที่ใหญ่ที่สุดของการใช้ useEffect คือการจัดการ dependency array ให้ถูกต้อง ลองมาดูเทคนิคต่างๆ ที่จะช่วยให้ชีวิตคุณง่ายขึ้น

1. การใช้ useCallback ร่วมกับ useEffect

เมื่อคุณต้องการส่งฟังก์ชันเป็น dependency ควรใช้ useCallback เพื่อคง reference ของฟังก์ชัน

import React, { useState, useEffect, useCallback } from 'react';

function SearchComponent() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);

  // useCallback ทำให้ฟังก์ชันนี้มี reference คงที่ ถ้า query ไม่เปลี่ยน
  const searchAPI = useCallback(async (searchTerm) => {
    if (!searchTerm.trim()) {
      setResults([]);
      return;
    }
    
    const response = await fetch(`/api/search?q=${searchTerm}`);
    const data = await response.json();
    setResults(data);
  }, []); // ไม่มี dependencies เพราะไม่ได้ใช้ค่าจากภายนอก

  useEffect(() => {
    // Debounce: รอ 300ms หลังจาก user หยุดพิมพ์
    const timerId = setTimeout(() => {
      searchAPI(query);
    }, 300);

    return () => clearTimeout(timerId);
  }, [query, searchAPI]); // searchAPI มี reference คงที่ จึงไม่ทำให้ effect ทำงานบ่อย

  return (
    <div>
      <input
        type="text"
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder="ค้นหา..."
      />
      <ul>
        {results.map(item => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </div>
  );
}

2. การใช้ useRef เพื่อเก็บค่าที่ไม่ต้อง trigger re-render

บางครั้งเราต้องการค่าล่าสุดใน effect โดยไม่ต้องใส่ dependency ที่ทำให้ effect ทำงานใหม่

import React, { useState, useEffect, useRef } from 'react';

function ChatComponent({ roomId }) {
  const [messages, setMessages] = useState([]);
  const latestMessagesRef = useRef(messages);
  
  // อัปเดต ref ทุกครั้งที่ messages เปลี่ยน
  useEffect(() => {
    latestMessagesRef.current = messages;
  }, [messages]);

  useEffect(() => {
    const socket = new WebSocket(`wss://chat.example.com/rooms/${roomId}`);
    
    socket.onmessage = (event) => {
      const newMessage = JSON.parse(event.data);
      
      // ใช้ latestMessagesRef.current แทน messages โดยตรง
      // เพื่อให้ได้ค่าล่าสุดโดยไม่ต้องใส่ messages ใน dependency
      setMessages(prev => [...prev, newMessage]);
      
      // ถ้าต้องการ log ข้อความล่าสุด
      console.log('Current messages count:', latestMessagesRef.current.length);
    };

    return () => {
      socket.close();
    };
  }, [roomId]); // ไม่ต้องใส่ messages ใน dependency

  return (
    <div>
      {messages.map((msg, index) => (
        <div key={index}>{msg.text}</div>
      ))}
    </div>
  );
}

3. การใช้ ESLint Plugin สำหรับ React Hooks

หนึ่งในเครื่องมือที่มีประโยชน์มากที่สุดคือ eslint-plugin-react-hooks ซึ่งจะช่วยตรวจสอบ dependency array โดยอัตโนมัติ

// ติดตั้ง
// npm install eslint-plugin-react-hooks --save-dev

// ใน .eslintrc.json
{
  "plugins": ["react-hooks"],
  "rules": {
    "react-hooks/rules-of-hooks": "error", // ห้ามใช้ Hooks ผิดที่
    "react-hooks/exhaustive-deps": "warn" // เตือนเมื่อ dependency ไม่ครบ
  }
}

Real-World Use Cases — ตัวอย่างการใช้งานจริง

มาดูตัวอย่างการประยุกต์ใช้ useEffect ในสถานการณ์จริงที่ซับซ้อนมากขึ้น

Use Case 1: Infinite Scroll Component

import React, { useState, useEffect, useCallback, useRef } from 'react';

function InfiniteScrollList() {
  const [items, setItems] = useState([]);
  const [page, setPage] = useState(1);
  const [hasMore, setHasMore] = useState(true);
  const [isLoading, setIsLoading] = useState(false);
  const observerRef = useRef(null);
  const lastItemRef = useCallback(node => {
    if (isLoading) return;
    if (observerRef.current) observerRef.current.disconnect();
    
    observerRef.current = new IntersectionObserver(entries => {
      if (entries[0].isIntersecting && hasMore) {
        setPage(prev => prev + 1);
      }
    });
    
    if (node) observerRef.current.observe(node);
  }, [isLoading, hasMore]);

  useEffect(() => {
    if (!hasMore) return;
    
    let isCancelled = false;
    
    async function fetchMore() {
      setIsLoading(true);
      try {
        const response = await fetch(`/api/items?page=${page}&limit=20`);
        const data = await response.json();
        
        if (!isCancelled) {
          setItems(prev => [...prev, ...data.items]);
          setHasMore(data.hasMore);
        }
      } catch (error) {
        console.error('Failed to load items:', error);
      } finally {
        if (!isCancelled) {
          setIsLoading(false);
        }
      }
    }
    
    fetchMore();
    
    return () => {
      isCancelled = true;
    };
  }, [page, hasMore]);

  return (
    <div>
      {items.map((item, index) => {
        const isLastItem = index === items.length - 1;
        return (
          <div
            key={item.id}
            ref={isLastItem ? lastItemRef : null}
            style={{ padding: '20px', borderBottom: '1px solid #ccc' }}
          >
            {item.name}
          </div>
        );
      })}
      {isLoading && <div>กำลังโหลด...</div>}
    </div>
  );
}

Use Case 2: Form Auto-save

import React, { useState, useEffect, useRef } from 'react';

function AutoSaveForm() {
  const [formData, setFormData] = useState({
    title: '',
    content: '',
    tags: []
  });
  const [lastSaved, setLastSaved] = useState(null);
  const [isSaving, setIsSaving] = useState(false);
  const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
  const initialDataRef = useRef(formData);

  // ตรวจสอบการเปลี่ยนแปลง
  useEffect(() => {
    const isDifferent = JSON.stringify(formData) !== JSON.stringify(initialDataRef.current);
    setHasUnsavedChanges(isDifferent);
  }, [formData]);

  // Auto-save ทุก 5 วินาที ถ้ามีการเปลี่ยนแปลง
  useEffect(() => {
    if (!hasUnsavedChanges) return;
    
    const timerId = setInterval(async () => {
      setIsSaving(true);
      try {
        const response = await fetch('/api/drafts', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(formData)
        });
        
        if (response.ok) {
          setLastSaved(new Date().toLocaleTimeString());
          initialDataRef.current = formData;
          setHasUnsavedChanges(false);
        }
      } catch (error) {
        console.error('Auto-save failed:', error);
      } finally {
        setIsSaving(false);
      }
    }, 5000);
    
    return () => clearInterval(timerId);
  }, [hasUnsavedChanges, formData]);

  // เตือนเมื่อ user จะปิดหน้าต่างโดยที่ยังไม่ได้บันทึก
  useEffect(() => {
    function handleBeforeUnload(e) {
      if (hasUnsavedChanges) {
        e.preventDefault();
        e.returnValue = '';
      }
    }
    
    window.addEventListener('beforeunload', handleBeforeUnload);
    return () => window.removeEventListener('beforeunload', handleBeforeUnload);
  }, [hasUnsavedChanges]);

  return (
    <div>
      <input
        type="text"
        value={formData.title}
        onChange={e => setFormData(prev => ({ ...prev, title: e.target.value }))}
        placeholder="หัวข้อ"
      />
      <textarea
        value={formData.content}
        onChange={e => setFormData(prev => ({ ...prev, content: e.target.value }))}
        placeholder="เนื้อหา"
      />
      <div>
        {isSaving && <span>กำลังบันทึกอัตโนมัติ...</span>}
        {lastSaved && <span>บันทึกล่าสุด: {lastSaved}</span>}
        {hasUnsavedChanges && <span style={{color: 'orange'}}> ยังไม่ได้บันทึก</span>}
      </div>
    </div>
  );
}

Use Case 3: การจัดการ Authentication State

import React, { useState, useEffect, createContext, useContext } from 'react';

const AuthContext = createContext(null);

function AuthProvider({ children }) {
  const [user, setUser] = useState(null);
  const [isLoading, setIsLoading] = useState(true);

  // ตรวจสอบ token เมื่อ mount
  useEffect(() => {
    async function checkAuth() {
      const token = localStorage.getItem('auth_token');
      
      if (!token) {
        setIsLoading(false);
        return;
      }
      
      try {
        const response = await fetch('/api/auth/verify', {
          headers: { 'Authorization': `Bearer ${token}` }
        });
        
        if (response.ok) {
          const userData = await response.json();
          setUser(userData);
        } else {
          // Token หมดอายุ
          localStorage.removeItem('auth_token');
        }
      } catch (error) {
        console.error('Auth check failed:', error);
      } finally {
        setIsLoading(false);
      }
    }
    
    checkAuth();
  }, []);

  // Refresh token อัตโนมัติทุก 30 นาที
  useEffect(() => {
    if (!user) return;
    
    const intervalId = setInterval(async () => {
      try {
        const response = await fetch('/api/auth/refresh', {
          method: 'POST',
          headers: { 'Authorization': `Bearer ${localStorage.getItem('auth_token')}` }
        });
        
        if (response.ok) {
          const { token } = await response.json();
          localStorage.setItem('auth_token', token);
        }
      } catch (error) {
        console.error('Token refresh failed:', error);
      }
    }, 30 * 60 * 1000); // 30 นาที
    
    return () => clearInterval(intervalId);
  }, [user]);

  const login = async (email, password) => {
    const response = await fetch('/api/auth/login', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ email, password })
    });
    
    if (!response.ok) throw new Error('Login failed');
    
    const { token, user: userData } = await response.json();
    localStorage.setItem('auth_token', token);
    setUser(userData);
  };

  const logout = () => {
    localStorage.removeItem('auth_token');
    setUser(null);
  };

  return (
    <AuthContext.Provider value={{ user, isLoading, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
}

การ Debug useEffect — เครื่องมือและเทคนิค

การ debug useEffect อาจเป็นเรื่องท้าทาย โดยเฉพาะเมื่อมีหลาย effect และ dependencies ที่ซับซ้อน ต่อไปนี้คือเทคนิคที่มีประโยชน์

1. การใช้ React DevTools

React DevTools เวอร์ชันล่าสุดมี Profiler ที่สามารถแสดงว่า effect ไหนทำงานเมื่อไหร่และใช้เวลานานเท่าไหร่

2. การ Log Effect Lifecycle

import React, { useEffect, useRef } from 'react';

function DebugEffect({ name, deps, children }) {
  const mountCount = useRef(0);
  
  useEffect(() => {
    mountCount.current += 1;
    console.log(`🔄 [${name}] Effect ทำงานครั้งที่ ${mountCount.current}`);
    console.log(`   Dependencies:`, deps);
    
    return () => {
      console.log(`🧹 [${name}] Cleanup ทำงาน`);
    };
  }, deps); // eslint-disable-line react-hooks/exhaustive-deps

  return children;
}

// วิธีใช้งาน
function MyComponent() {
  const [count, setCount] = useState(0);
  
  return (
    <DebugEffect name="CountEffect" deps={[count]}>
      <div>
        <p>Count: {count}</p>
        <button onClick={() => setCount(c => c + 1)}>+1</button>
      </div>
    </DebugEffect>
  );
}

3. การใช้ useWhyDidYouUpdate Hook

import { useEffect, useRef } from 'react';

function useWhyDidYouUpdate(name, props) {
  const previousProps = useRef();

  useEffect(() => {
    if (previousProps.current) {
      const allKeys = Object.keys({ ...previousProps.current, ...props });
      const changesObj = {};

      allKeys.forEach(key => {
        if (previousProps.current[key] !== props[key]) {
          changesObj[key] = {
            from: previousProps.current[key],
            to: props[key]
          };
        }
      });

      if (Object.keys(changesObj).length) {
        console.log(`[useWhyDidYouUpdate] ${name}`, changesObj);
      }
    }

    previousProps.current = props;
  });
}

สรุปและแนวทางสำหรับปี 2026

useEffect ยังคงเป็นเครื่องมือสำคัญในการพัฒนา React application แม้ในปี 2026 แนวโน้มและ best practices ที่ควรจับตามองมีดังนี้:

  • React Server Components (RSC) — ลดความจำเป็นในการใช้ useEffect สำหรับ data fetching บน client
  • use() Hook — API ใหม่ที่อาจมาแทนที่ useEffect ในบางกรณีสำหรับการจัดการ promises
  • Concurrent Features — การใช้ useEffect ร่วมกับ Suspense และ transition
  • Custom Hooks มากขึ้น — การแยก business logic ออกจาก UI component

Summary

useEffect เป็นหัวใจสำคัญของการจัดการ side effects ใน React functional component โดยมีหลักการสำคัญที่ควรจำ:

  • เข้าใจ Dependency Array — มันคือกุญแจสำคัญที่ควบคุมว่า effect จะทำงานเมื่อไหร่
  • Cleanup เสมอ — ป้องกัน memory leak โดยเฉพาะกับ subscriptions, timers, event listeners
  • แยก Effect ตามหน้าที่ — อย่ารวมทุกอย่างไว้ใน effect เดียว
  • ใช้ ESLint Plugin — ให้เครื่องมือช่วยตรวจสอบ dependency ที่ขาดหาย
  • ใช้ Custom Hooks — เพื่อทำให้ code อ่านง่ายและนำกลับมาใช้ใหม่ได้
  • จัดการ Loading/Error State — ให้ user experience ที่ดี

การฝึกฝนและทำความเข้าใจ useEffect อย่างลึกซึ้งจะช่วยให้คุณพัฒนา React application ที่มีคุณภาพสูง ปราศจากบั๊กที่เกิดจาก side effects และมี performance ที่ดีเยี่ยม อย่าลืมว่าเครื่องมือและแนวทางปฏิบัติที่ดีที่สุดมีการพัฒนาอยู่เสมอ ดังนั้นควรติดตามเอกสารทางการของ React และ community อย่างสม่ำเสมอ

บทความนี้เป็นส่วนหนึ่งของ SiamCafe Blog — แหล่งความรู้ด้านเทคโนโลยีสำหรับนักพัฒนาไทย

จัดส่งรวดเร็วส่งด่วนทั่วประเทศ
รับประกันสินค้าเคลมง่าย มีใบรับประกัน
ผ่อนชำระได้บัตรเครดิต 0% สูงสุด 10 เดือน
สะสมแต้ม รับส่วนลดส่วนลดและคะแนนสะสม

© 2026 SiamLancard — จำหน่ายการ์ดแลน อุปกรณ์ Server และเครื่องพิมพ์ใบเสร็จ

SiamLancard
Logo
Free Forex EA — XM Signal · SiamCafe Blog · SiamLancard · Siam2R · iCafeFX
iCafeForex.com - สอนเทรด Forex | SiamCafe.net
Shopping cart