DevToolBoxฟรี
บล็อก

คู่มือ React Hooks ฉบับสมบูรณ์

15 minโดย DevToolBox

React Hooks ปฏิวัติวิธีการเขียน React components ตั้งแต่ React 16.8 Hooks ช่วยให้คุณใช้ state, lifecycle และ context ใน functional components ได้ คู่มือ React Hooks ฉบับสมบูรณ์นี้ครอบคลุมทุก Hook พร้อมตัวอย่างจริง

useState: จัดการ State

useState เป็น Hook พื้นฐานที่สุด

import { useState } from 'react';

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

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <button onClick={() => setCount(prev => prev - 1)}>Decrement</button>
    </div>
  );
}

// Lazy initialization — runs only on first render
const [data, setData] = useState(() => {
  return JSON.parse(localStorage.getItem('data') || '{}');
});

// Updating objects — always create a new object
const [user, setUser] = useState({ name: '', age: 0 });
setUser(prev => ({ ...prev, name: 'Alice' }));

// Updating arrays — use spread or filter/map
const [items, setItems] = useState<string[]>([]);
setItems(prev => [...prev, 'new item']);
setItems(prev => prev.filter(item => item !== 'remove me'));

useEffect: Side Effects และ Lifecycle

useEffect ช่วยให้ทำ side effects ใน function components

import { useEffect, useState } from 'react';

function UserProfile({ userId }: { userId: string }) {
  const [user, setUser] = useState(null);

  // Runs when userId changes (componentDidMount + componentDidUpdate)
  useEffect(() => {
    let cancelled = false;
    fetch(`/api/users/${userId}`)
      .then(res => res.json())
      .then(data => {
        if (!cancelled) setUser(data);
      });

    // Cleanup function (componentWillUnmount)
    return () => { cancelled = true; };
  }, [userId]); // dependency array

  return <div>{user?.name}</div>;
}

// Run once on mount
useEffect(() => {
  console.log('Component mounted');
  return () => console.log('Component unmounted');
}, []); // empty dependency array

// Run on every render (rarely needed)
useEffect(() => {
  console.log('Component rendered');
}); // no dependency array

useContext: ใช้งาน Context

useContext subscribe React context โดยไม่ต้อง Consumer

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

// 1. Create a context with a default value
interface ThemeContextType {
  theme: 'light' | 'dark';
  toggleTheme: () => void;
}
const ThemeContext = createContext<ThemeContextType | null>(null);

// 2. Create a provider component
function ThemeProvider({ children }: { children: React.ReactNode }) {
  const [theme, setTheme] = useState<'light' | 'dark'>('light');
  const toggleTheme = () => setTheme(t => t === 'light' ? 'dark' : 'light');

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

// 3. Consume context with useContext
function ThemeButton() {
  const ctx = useContext(ThemeContext);
  if (!ctx) throw new Error('Must be inside ThemeProvider');

  return (
    <button onClick={ctx.toggleTheme}>
      Current: {ctx.theme}
    </button>
  );
}

// 4. Custom hook for cleaner usage
function useTheme() {
  const ctx = useContext(ThemeContext);
  if (!ctx) throw new Error('useTheme must be used within ThemeProvider');
  return ctx;
}

useMemo: Cache การคำนวณราคาแพง

useMemo cache ผลลัพธ์ของการคำนวณราคาแพง

import { useMemo, useState } from 'react';

function ExpensiveList({ items, filter }: { items: Item[]; filter: string }) {
  // Only recalculates when items or filter changes
  const filteredItems = useMemo(() => {
    console.log('Filtering...');
    return items.filter(item =>
      item.name.toLowerCase().includes(filter.toLowerCase())
    );
  }, [items, filter]);

  // Memoize a sorted copy
  const sortedItems = useMemo(() => {
    return [...filteredItems].sort((a, b) => a.name.localeCompare(b.name));
  }, [filteredItems]);

  return (
    <ul>
      {sortedItems.map(item => <li key={item.id}>{item.name}</li>)}
    </ul>
  );
}

// Do NOT overuse useMemo — only for truly expensive computations
// Simple operations do not need memoization
const total = useMemo(() => items.reduce((sum, i) => sum + i.price, 0), [items]);

useCallback: Cache ฟังก์ชัน

useCallback คืนเวอร์ชัน cache ของ callback

import { useCallback, useState, memo } from 'react';

// Child component wrapped in memo — only re-renders if props change
const SearchInput = memo(({ onSearch }: { onSearch: (q: string) => void }) => {
  console.log('SearchInput rendered');
  return <input onChange={e => onSearch(e.target.value)} />;
});

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

  // Without useCallback, a new function is created every render
  // causing SearchInput to re-render unnecessarily
  const handleSearch = useCallback((q: string) => {
    setQuery(q);
    fetch(`/api/search?q=${q}`)
      .then(res => res.json())
      .then(setResults);
  }, []); // stable reference

  return (
    <div>
      <SearchInput onSearch={handleSearch} />
      <ul>{results.map(r => <li key={r.id}>{r.title}</li>)}</ul>
    </div>
  );
}

useRef: Mutable References

useRef คืน mutable ref object

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

function TextInputWithFocus() {
  // DOM reference
  const inputRef = useRef<HTMLInputElement>(null);

  useEffect(() => {
    inputRef.current?.focus(); // auto-focus on mount
  }, []);

  return <input ref={inputRef} placeholder="Auto-focused" />;
}

function StopWatch() {
  const [time, setTime] = useState(0);
  const intervalRef = useRef<NodeJS.Timeout | null>(null);

  const start = () => {
    intervalRef.current = setInterval(() => {
      setTime(t => t + 1);
    }, 1000);
  };

  const stop = () => {
    if (intervalRef.current) clearInterval(intervalRef.current);
  };

  // Track previous value
  const prevTimeRef = useRef(time);
  useEffect(() => { prevTimeRef.current = time; });

  return (
    <div>
      <p>Time: {time}s (prev: {prevTimeRef.current}s)</p>
      <button onClick={start}>Start</button>
      <button onClick={stop}>Stop</button>
    </div>
  );
}

useReducer: State Logic ที่ซับซ้อน

useReducer เป็นทางเลือกของ useState

import { useReducer } from 'react';

interface State {
  count: number;
  step: number;
}

type Action =
  | { type: 'increment' }
  | { type: 'decrement' }
  | { type: 'reset' }
  | { type: 'setStep'; payload: number };

function reducer(state: State, action: Action): State {
  switch (action.type) {
    case 'increment':
      return { ...state, count: state.count + state.step };
    case 'decrement':
      return { ...state, count: state.count - state.step };
    case 'reset':
      return { count: 0, step: 1 };
    case 'setStep':
      return { ...state, step: action.payload };
    default:
      return state;
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, { count: 0, step: 1 });

  return (
    <div>
      <p>Count: {state.count}</p>
      <input
        type="number"
        value={state.step}
        onChange={e => dispatch({ type: 'setStep', payload: Number(e.target.value) })}
      />
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
      <button onClick={() => dispatch({ type: 'reset' })}>Reset</button>
    </div>
  );
}

Custom Hooks: Logic ที่ใช้ซ้ำได้

Custom Hooks แยก component logic เป็นฟังก์ชันที่ใช้ซ้ำได้

// useLocalStorage — persist state to localStorage
function useLocalStorage<T>(key: string, initialValue: T) {
  const [value, setValue] = useState<T>(() => {
    try {
      const item = localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch {
      return initialValue;
    }
  });

  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);

  return [value, setValue] as const;
}

// useDebounce — debounce a rapidly changing value
function useDebounce<T>(value: T, delay: number): T {
  const [debounced, setDebounced] = useState(value);
  useEffect(() => {
    const timer = setTimeout(() => setDebounced(value), delay);
    return () => clearTimeout(timer);
  }, [value, delay]);
  return debounced;
}

// useFetch — generic data fetching
function useFetch<T>(url: string) {
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<Error | null>(null);

  useEffect(() => {
    let cancelled = false;
    setLoading(true);
    fetch(url)
      .then(res => res.json())
      .then(data => { if (!cancelled) { setData(data); setLoading(false); } })
      .catch(err => { if (!cancelled) { setError(err); setLoading(false); } });
    return () => { cancelled = true; };
  }, [url]);

  return { data, loading, error };
}

// Usage
function App() {
  const [name, setName] = useLocalStorage('name', '');
  const debouncedName = useDebounce(name, 300);
  const { data, loading } = useFetch<User[]>(`/api/search?q=${debouncedName}`);
}

กฎของ Hooks

กฎสำคัญ 2 ข้อของ Hooks

  • เรียก Hooks ที่ระดับบนสุดเท่านั้น
  • เรียก Hooks จากฟังก์ชัน React เท่านั้น
// WRONG — Hook inside a condition
function Bad({ isLoggedIn }) {
  if (isLoggedIn) {
    const [user, setUser] = useState(null); // breaks Hook order
  }
}

// CORRECT — condition inside the Hook
function Good({ isLoggedIn }) {
  const [user, setUser] = useState(null);
  useEffect(() => {
    if (isLoggedIn) fetchUser().then(setUser);
  }, [isLoggedIn]);
}

กับดักทั่วไปและวิธีแก้

Stale closure ใน useEffect

เมื่ออ้างอิง state โดยไม่รวมใน dependencies

// BUG: stale closure
function Timer() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    const id = setInterval(() => {
      console.log(count); // always logs 0 (stale!)
      setCount(count + 1); // always sets to 1
    }, 1000);
    return () => clearInterval(id);
  }, []); // count is missing from dependencies
}

// FIX: use functional update
useEffect(() => {
  const id = setInterval(() => {
    setCount(prev => prev + 1); // always uses latest value
  }, 1000);
  return () => clearInterval(id);
}, []); // safe with functional update

Loop ไม่สิ้นสุดกับ useEffect

ตั้ง state ใน useEffect โดยไม่มี dependencies ที่ถูกต้อง

// BUG: infinite loop
useEffect(() => {
  setCount(count + 1); // triggers re-render, which runs effect again
}); // no dependency array = runs every render

// FIX: add dependency array
useEffect(() => {
  if (count < 10) setCount(count + 1);
}, [count]); // only runs when count changes

Object/array dependencies

Objects และ arrays เปรียบเทียบโดย reference

// BUG: new object every render
function App() {
  const options = { page: 1, limit: 10 }; // new ref each render
  useEffect(() => {
    fetchData(options);
  }, [options]); // runs every render!
}

// FIX: useMemo to stabilize the reference
function App() {
  const options = useMemo(() => ({ page: 1, limit: 10 }), []);
  useEffect(() => {
    fetchData(options);
  }, [options]); // stable reference
}

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

React Hooks คืออะไร?

ฟังก์ชันที่ช่วยให้ใช้ฟีเจอร์ React ใน functional components

ความแตกต่างระหว่าง useMemo กับ useCallback?

useMemo cache ค่า, useCallback cache ฟังก์ชัน

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

เมื่อ state logic ซับซ้อน

ใช้ Hooks ใน class components ได้ไหม?

ไม่ได้ ใช้ได้เฉพาะ functional components

หลีกเลี่ยง loop ไม่สิ้นสุดอย่างไร?

ระบุ dependency array ที่ถูกต้องเสมอ

React Hooks จำเป็นสำหรับการพัฒนา React สมัยใหม่

𝕏 Twitterin LinkedIn
บทความนี้มีประโยชน์ไหม?

อัปเดตข่าวสาร

รับเคล็ดลับการพัฒนาและเครื่องมือใหม่ทุกสัปดาห์

ไม่มีสแปม ยกเลิกได้ตลอดเวลา

ลองเครื่องมือที่เกี่ยวข้อง

{ }JSON FormatterJSTypeScript to JavaScript&;HTML Entity Encoder / Decoder

บทความที่เกี่ยวข้อง

React Server Components: คู่มือฉบับสมบูรณ์ 2026

เชี่ยวชาญ React Server Components: สถาปัตยกรรม, การดึงข้อมูล, streaming และการย้าย

เมธอด Array JavaScript: Cheat Sheet ฉบับสมบูรณ์พร้อมตัวอย่าง

อ้างอิงเมธอด array JavaScript ฉบับสมบูรณ์ ครอบคลุม map, filter, reduce, find, sort, flat, flatMap, splice และเมธอด ES2023

TypeScript Type Guards: คู่มือการตรวจสอบประเภทขณะรันไทม์

เชี่ยวชาญ TypeScript type guards: typeof, instanceof, in และ guard แบบกำหนดเอง