ไทย

เรียนรู้วิธีใช้ Custom Hooks ของ React เพื่อแยกและนำ Logic ของ Component กลับมาใช้ใหม่ ปรับปรุงการดูแลรักษาโค้ด การทดสอบ และสถาปัตยกรรมแอปพลิเคชันโดยรวม

Custom Hooks ของ React: แยก Logic ของ Component เพื่อนำกลับมาใช้ใหม่

React hooks ได้ปฏิวัติวิธีการเขียน React component ของเรา โดยเสนอวิธีที่สง่างามและมีประสิทธิภาพมากขึ้นในการจัดการ state และ side effects ในบรรดา hooks ที่มีอยู่ custom hooks โดดเด่นในฐานะเครื่องมือที่มีประสิทธิภาพสำหรับการแยกและนำ logic ของ component กลับมาใช้ใหม่ บทความนี้ให้คำแนะนำที่ครอบคลุมเกี่ยวกับการทำความเข้าใจและใช้งาน custom hooks ของ React ช่วยให้คุณสร้างแอปพลิเคชันที่ดูแลรักษาได้ง่ายขึ้น ทดสอบได้ดีขึ้น และปรับขนาดได้

Custom Hooks ของ React คืออะไร?

โดยพื้นฐานแล้ว custom hook คือฟังก์ชัน JavaScript ที่ชื่อขึ้นต้นด้วย "use" และสามารถเรียกใช้ hooks อื่นๆ ได้ ช่วยให้คุณสามารถแยก logic ของ component ออกเป็นฟังก์ชันที่นำกลับมาใช้ใหม่ได้ ทำให้ไม่ต้องซ้ำซ้อนกัน และส่งเสริมโครงสร้าง component ที่สะอาดกว่า custom hooks ต่างจาก React component ทั่วไปตรงที่จะไม่แสดง UI ใดๆ แต่จะทำการห่อหุ้ม logic เท่านั้น

ลองนึกภาพว่าเป็นฟังก์ชันที่นำกลับมาใช้ใหม่ได้ ซึ่งสามารถเข้าถึง state และคุณสมบัติ lifecycle ของ React ได้ เป็นวิธีที่ยอดเยี่ยมในการแชร์ stateful logic ระหว่าง component ต่างๆ โดยไม่ต้องอาศัย higher-order components หรือ render props ซึ่งมักจะทำให้เกิดโค้ดที่อ่านและดูแลรักษาได้ยาก

ทำไมต้องใช้ Custom Hooks?

ประโยชน์ของการใช้ custom hooks มีมากมาย:

การสร้าง Custom Hook แรกของคุณ

เรามาแสดงตัวอย่างการสร้างและใช้งาน custom hook ด้วยตัวอย่างที่ใช้งานได้จริง นั่นคือ การดึงข้อมูลจาก API

ตัวอย่าง: useFetch - Hook สำหรับการดึงข้อมูล

สมมติว่าคุณต้องดึงข้อมูลจาก API ต่างๆ ในแอปพลิเคชัน React ของคุณบ่อยครั้ง แทนที่จะทำซ้ำ logic การ fetch ในแต่ละ component คุณสามารถสร้าง hook useFetch ได้


import { useState, useEffect } from 'react';

function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const abortController = new AbortController();
    const signal = abortController.signal;

    const fetchData = async () => {
      setLoading(true);
      try {
        const response = await fetch(url, { signal: signal });
        if (!response.ok) {
          throw new Error(`HTTP error! Status: ${response.status}`);
        }
        const json = await response.json();
        setData(json);
        setError(null); // ล้างข้อผิดพลาดก่อนหน้าใดๆ
      } catch (error) {
        if (error.name === 'AbortError') {
          console.log('Fetch aborted');
        } else {
          setError(error);
        }
        setData(null); // ล้างข้อมูลก่อนหน้าใดๆ
      } finally {
        setLoading(false);
      }
    };

    fetchData();

    return () => {
      abortController.abort(); // ฟังก์ชัน Cleanup เพื่อยกเลิกการ fetch เมื่อ unmount หรือ URL เปลี่ยน
    };
  }, [url]); // รัน effect อีกครั้งเมื่อ URL เปลี่ยน

  return { data, loading, error };
}

export default useFetch;

คำอธิบาย:

การใช้ Hook useFetch ใน Component

ตอนนี้มาดูกันว่าจะใช้ custom hook นี้ใน React component ได้อย่างไร:


import React from 'react';
import useFetch from './useFetch';

function UserList() {
  const { data: users, loading, error } = useFetch('https://jsonplaceholder.typicode.com/users');

  if (loading) return <p>กำลังโหลดผู้ใช้...</p>;
  if (error) return <p>ข้อผิดพลาด: {error.message}</p>;
  if (!users) return <p>ไม่พบผู้ใช้.</p>;

  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name} ({user.email})</li>
      ))}
    </ul>
  );
}

export default UserList;

คำอธิบาย:

รูปแบบ Custom Hook ขั้นสูง

นอกเหนือจากการดึงข้อมูลทั่วไป custom hooks สามารถใช้เพื่อห่อหุ้ม logic ที่ซับซ้อนมากขึ้นได้ นี่คือรูปแบบขั้นสูงบางส่วน:

1. การจัดการ State ด้วย useReducer

สำหรับสถานการณ์การจัดการ state ที่ซับซ้อนกว่า คุณสามารถรวม custom hooks เข้ากับ useReducer ได้ ซึ่งจะช่วยให้คุณจัดการการเปลี่ยนแปลง state ในลักษณะที่คาดเดาได้และเป็นระเบียบมากขึ้น


import { useReducer } from 'react';

const initialState = { count: 0 };

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      throw new Error();
  }
}

function useCounter() {
  const [state, dispatch] = useReducer(reducer, initialState);

  const increment = () => dispatch({ type: 'increment' });
  const decrement = () => dispatch({ type: 'decrement' });

  return { count: state.count, increment, decrement };
}

export default useCounter;

การใช้งาน:


import React from 'react';
import useCounter from './useCounter';

function Counter() {
  const { count, increment, decrement } = useCounter();

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
    </div>
  );
}

export default Counter;

2. การผสานรวม Context ด้วย useContext

Custom hooks ยังสามารถใช้เพื่อทำให้การเข้าถึง React Context ง่ายขึ้น แทนที่จะใช้ useContext โดยตรงใน component ของคุณ คุณสามารถสร้าง custom hook ที่ห่อหุ้ม logic การเข้าถึง context ได้


import { useContext } from 'react';
import { ThemeContext } from './ThemeContext'; // สมมติว่าคุณมี ThemeContext

function useTheme() {
  return useContext(ThemeContext);
}

export default useTheme;

การใช้งาน:


import React from 'react';
import useTheme from './useTheme';

function MyComponent() {
  const { theme, toggleTheme } = useTheme();

  return (
    <div style={{ backgroundColor: theme.background, color: theme.color }}>
      <p>นี่คือ component ของฉัน.</p>
      <button onClick={toggleTheme}>สลับ Theme</button>
    </div>
  );
}

export default MyComponent;

3. Debouncing และ Throttling

Debouncing และ throttling เป็นเทคนิคที่ใช้ควบคุมอัตราที่ฟังก์ชันถูกเรียกใช้ Custom hooks สามารถใช้เพื่อห่อหุ้ม logic นี้ ทำให้ง่ายต่อการใช้เทคนิคเหล่านี้กับ event handlers


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

function useDebounce(value, delay) {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => {
      clearTimeout(handler);
    };
  }, [value, delay]);

  return debouncedValue;
}

export default useDebounce;

การใช้งาน:


import React, { useState } from 'react';
import useDebounce from './useDebounce';

function SearchInput() {
  const [searchValue, setSearchValue] = useState('');
  const debouncedSearchValue = useDebounce(searchValue, 500); // Debounce เป็นเวลา 500ms

  useEffect(() => {
    // ทำการค้นหาด้วย debouncedSearchValue
    console.log('กำลังค้นหา:', debouncedSearchValue);
    // แทนที่ console.log ด้วย logic การค้นหาจริงของคุณ
  }, [debouncedSearchValue]);

  const handleChange = (event) => {
    setSearchValue(event.target.value);
  };

  return (
    <input
      type="text"
      value={searchValue}
      onChange={handleChange}
      placeholder="ค้นหา..."
    />
  );
}

export default SearchInput;

แนวทางปฏิบัติที่ดีที่สุดสำหรับการเขียน Custom Hooks

เพื่อให้แน่ใจว่า custom hooks ของคุณมีประสิทธิภาพและดูแลรักษาได้ง่าย ให้ปฏิบัติตามแนวทางปฏิบัติที่ดีที่สุดเหล่านี้:

ข้อควรพิจารณาในระดับสากล

เมื่อพัฒนาแอปพลิเคชันสำหรับผู้ชมทั่วโลก ให้คำนึงถึงสิ่งต่อไปนี้:

ตัวอย่าง: การจัดรูปแบบวันที่แบบนานาชาติด้วย Custom Hook


import { useState, useEffect } from 'react';
import { DateTimeFormat } from 'intl';

function useFormattedDate(date, locale) {
  const [formattedDate, setFormattedDate] = useState('');

  useEffect(() => {
    try {
      const formatter = new DateTimeFormat(locale, {
        year: 'numeric',
        month: 'long',
        day: 'numeric',
      });
      setFormattedDate(formatter.format(date));
    } catch (error) {
      console.error('ข้อผิดพลาดในการจัดรูปแบบวันที่:', error);
      setFormattedDate('Invalid Date');
    }
  }, [date, locale]);

  return formattedDate;
}

export default useFormattedDate;

การใช้งาน:


import React from 'react';
import useFormattedDate from './useFormattedDate';

function MyComponent() {
  const today = new Date();
  const enDate = useFormattedDate(today, 'en-US');
  const frDate = useFormattedDate(today, 'fr-FR');
  const deDate = useFormattedDate(today, 'de-DE');

  return (
    <div>
      <p>US Date: {enDate}</p>
      <p>French Date: {frDate}</p>
      <p>German Date: {deDate}</p>
    </div>
  );
}

export default MyComponent;

สรุป

Custom hooks ของ React เป็นกลไกที่มีประสิทธิภาพสำหรับการแยกและนำ logic ของ component กลับมาใช้ใหม่ ด้วยการใช้ custom hooks คุณสามารถเขียนโค้ดที่สะอาดกว่า ดูแลรักษาได้ง่ายขึ้น และทดสอบได้ดีขึ้น เมื่อคุณมีความชำนาญกับ React มากขึ้น การเรียนรู้ custom hooks จะช่วยเพิ่มความสามารถของคุณในการสร้างแอปพลิเคชันที่ซับซ้อนและปรับขนาดได้ อย่าลืมปฏิบัติตามแนวทางปฏิบัติที่ดีที่สุดและพิจารณาปัจจัยระดับโลกเมื่อพัฒนา custom hooks เพื่อให้แน่ใจว่ามีประสิทธิภาพและสามารถเข้าถึงได้สำหรับผู้ชมที่หลากหลาย โอบรับพลังของ custom hooks และยกระดับทักษะการพัฒนา React ของคุณ!